一篇文章说完Flutter页面路由导航及传参

目录

前言

动态路由

静态路由

静态路由传参

Fluro 实现路由导航与传参


前言

在 Flutter 中,App 多个页面之间的跳转是由 Navigator(导航器)来管理的,如常见的 Navigator.push 跳转到下一页,Navigator.pop 回到上一页,同时也会涉及到页面之间的参数传递。本文主要介绍一下动态路由、静态路由及第三方路由插件 Fluro,它们在页面跳转、参数传递的区别和各自的优缺点,日常开发选择合适的即可。

动态路由

动态路由是不需要显示声明的,通过代码来实现,如下面这样的:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "FlutterToDo",
      theme: ThemeData(
        brightness: Brightness.dark,
      ),
      // 直接将 HomePage() 赋值给 MaterialApp 的 home
      home: HomePage(),
    );
  }
}

页面跳转通过 Navigator.push,在跳转到 SecondPage.dart 页面时,如果有参数时需要通过构造函数将参数传递给 SecondPage.dart,如这里的参数是 pageTitle。

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("FlutterToDo"),
      ),
      body: Center(
        child: RaisedButton(
          child: Text("这是第一个界面,点击进入第二个界面"),
          onPressed: () {
            // 点击跳转页面,无参数
              // Navigator.push
              // context,
              //     MaterialPageRoute(
              //     builder: (context) => SecondPage(),
              // ));
            
            // 带参数的页面跳转
            Navigator.push(
                context,
                MaterialPageRoute(
                  // 参数:pageTitle
                  builder: (context) => SecondPage(pageTitle: "SecondPage"),
                  // 调用 then 等待接收 SecondPage 返回的数据
                )).then((value) => print(value));
            },                
          },
        ),
      ),
    );
  }
}

SecondPage.dart页面代码,参数 pageTitle依赖接收上个页面传递过来的数据并直接作为当前页面的标题。

import 'package:flutter/material.dart';

class SecondPage extends StatelessWidget {
  final String pageTitle;

  SecondPage({Key key, this.pageTitle = "第二个界面"});
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(pageTitle),
      ),
      body: Center(
        child: RaisedButton(
          child: Text("这是第二个界面,点击进入第一个界面"),
          onPressed: () {
            // 返回上一个界面,
            // Navigator.pop(context);
            // 携带参数上一个界面,"SecondPage pop" 为传递给上一个页面的参数。
            Navigator.pop(context, "SecondPage pop");
          },
        ),
      ),
    );
  }
}

动态路由简单直接,传参时参数名称定义明确,调用者通过构造函数就能提示具体参数,使用起来比较方便,不用特地的去声明路由,缺点是缺乏统一管理,当前页面需要导入要跳转的页面,页面之间有强依赖,项目后期业务逻辑增多、结构变复杂,维护成本也就增加,不利于业务模块组件化。

静态路由

静态路由又称命名路由,其实现方式是在跳转之前,需要通过在MaterialApp内的routes属性内显式声明路由的名称,代码实现如下:

import 'package:flutter/material.dart';
import 'pages/HomePage.dart';
import 'pages/SecondPage.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "FlutterToDo",
      // 默认加载的页面
      initialRoute: '/',
      // 显式声明路由列表
      routes: {
        '/': (context) => HomePage(),
        '/secondPage': (context) => SecondPage(),
      },
    );
  }
}

此时首页的代码实现及点击按钮的跳转逻辑如下:

// 跳转到第二个界面,
Navigator.pushNamed(context, '/secondPage');

这里的 /secondPage 就是上面 MyApp - > MaterialApp-> routes 中定义的路由,名称必须保持一致。SecondPage 页面点击按钮返回:

// 返回上一个界面
Navigator.pop(context);

静态路由传参

静态路由是通过 MyApp - > MaterialApp-> onGenerateRoute 属性来处理。

import 'package:flutter/material.dart';
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "FlutterToDo",
      initialRoute: '/', // 默认界面
      // 当页面跳转时进行参数处理
      onGenerateRoute: (RouteSettings settings) {
        // ......
      },
    );
  }
}

查看 onGenerateRoute 的源码定义可以看到,根据所给的 route settings 来返回一个 Route<dynamic>

而打开 RouteSettings 类的实现看到其构造函数有 namearguments,其中 name 为路由名称,arguments 就是传递的参数,其类型为 Object

将静态路由的代码优化一下之后:

import 'package:flutter/material.dart';
import 'pages/HomePage.dart';
import 'pages/SecondPage.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // routes中声明所有的页面
  final routes = {
    '/': (context, {arguments}) => HomePage(),
    '/secondPage': (context, {arguments}) => SecondPage(arguments: arguments),
  };

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "FlutterToDo",
      initialRoute: '/', // 默认界面
      // 当页面跳转时进行参数处理
      onGenerateRoute: (RouteSettings settings) {
        // 1. 根据路由名称从上面声明的 routes 取出类似于 (context, {arguments}) => HomePage()的 pageBuilder函数体
        var pageBuilder = routes[settings.name];
        if (pageBuilder != null) {
          // 2. 判断是否有携带的参数
          if (settings.arguments != null) {
            // 创建路由页面并携带参数
            // 3. 传递 context 和 settings.arguments给 pageBuilder 函数体执行,
            // 并返回一个Widget页面给到 MaterialPageRoute 的 builder
            return MaterialPageRoute(
                builder: (context) =>
                    pageBuilder(context, arguments: settings.arguments));
          } else {
            // 3. 无参数的情况
            return MaterialPageRoute(
                builder: (context) => pageBuilder(context));
          }
        }
        // 默认返回 HomePage页面
        return MaterialPageRoute(builder: (context) => HomePage());
      },
    );
  }
}

HomePage 点击按钮的跳转和传参:

// 通过arguments指定参数
Navigator.pushNamed(context, "/secondPage", arguments: {'title': "SecondPage title"}).then((value) {
  // 退出 SecondPage 时返回的参数
  print(value) // 返回传递数据 SecondPage pop
});

 此时 SecondPage 页面获取参数代码如下:

import 'package:flutter/material.dart';

class SecondPage extends StatelessWidget {
  final Map arguments;
  SecondPage({Key key, this.arguments});
  
  @override
  Widget build(BuildContext context) {
    String title = arguments != null ? arguments['title'] : "SecondPage";
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: RaisedButton(
          child: Text("这是第二个界面,点击进入第一个界面"),
          onPressed: () {
            // 返回上一个界面
            Navigator.pop(context, "返回传递数据 SecondPage pop");
          },
        ),
      ),
    );
  }
}

从上面可以看出,静态路由及传参优点也很明显,声明路由统一在一个位置定义,方便管理,当然也可以创建一个管理类(如:RouteManager)将 routes 中的路由 path 声明为静态的变量。

class RouteManager {
  static String homePage = "/";
  static String secondPage = "/secondPage";
 
  static routes = {
    homePage: (context, {arguments}) => HomePage(),
    secondPage: (context, {arguments}) => SecondPage(arguments: arguments),
  };
}

此时直接使用 RouteManager.secondPage 就可以了,相比于动态路由,跳转时不再需要导入 SecondPage,页面直接的耦合度降低。缺点通过 arguments 不够直接准确,同时在 SecondPage也需要定义一个 Map 来接收,这里的Map结构就需要调用者和使用者都能明确且准确无误填入参数字段,根据参数字段取出数据,对调用者和使用者都有一定的要求,同时也非常容易出错。

Fluro 实现路由导航与传参

第三方插件 fluro 在 pub地址,接下来,我们通过代码来看看 fluro 是如何解决 动态路由带来的页面耦合度过高和静态路由传参不方便的问题。

在pubspec.yaml中导入插件:

fluro: ^2.0.3

此时 MyApp 代码:

import 'package:flutter/material.dart';

class MyApp extends StatelessWidget {
  MyApp({Key? key}) : super(key: key) {
    MyRoutes.configureRoutes();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter 技术实践',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      onGenerateRoute: MyRoutes.router.generator,
      initialRoute: MyRoutes.root,
    );
  }
}

MyRoutes 的实现实际上就是封装的 FluroRouter,路由的定义也统一在这个类里面完成,其实现如下: 

import 'package:fluro/fluro.dart';

class MyRoutes {
  static FluroRouter router = FluroRouter();
  static String testRoutePage = "/testRoutePage";
  static String secondPage = "/testRoutePage/secondPage";

  static void configureRoutes() {

    router.notFoundHandler = Handler(
        handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
      return const Text("ROUTE WAS NOT FOUND !!!");
    });
    router.define(testRoutePage,
        handler: Handler(handlerFunc: (_, __) => const TestRoutePage()));

    router.define(secondPage,
        handler: Handler(handlerFunc: (_, params) {
          String title = params['title']?.first ?? "";
          return SecondPage(title: title,);
        }));
  }
}

TestRoutePage 跳转到 SecondPage 并传递 title 参数。

class TestRoutePage extends StatelessWidget {
  const TestRoutePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: RaisedButton(
          child: const Text("TestRoutePage"),
          onPressed: () {
            // 跳转到 SecondPage,传参方式和get请求很像
            MyRoutes.router
                .navigateTo(context, MyRoutes.secondPage + "?title=hello")
                .then((value) => print(value));
          },
        ),
      ),
    );
  }
}

SecondPage 页面实现

class SecondPage extends StatelessWidget {
  String? title;

  SecondPage({Key? key, this.title = "第二个页面"}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: RaisedButton(
          child: Text(title ?? ""),
          onPressed: () {
            Navigator.pop(context, "SecondPage pop 参数");
          },
        ),
      ),
    );
  }
}

相比较于动态路由和静态路由,Fluro 传参方式更加简洁,运行在Web上时,而且通用性更高,当然还有更多好用的功能,这里只是简单的介绍 Fluro 的基本使用,详细用法请移步查看 Fluro 相关文档。

小结:日常开发中,路由导航和传参使用频率非常之高,选择合适的路由管理方式不仅有利于项目的维护和扩展,后期如果需要兼容Web等平台时,也能胜任。

本文代码可直接打开 flutter_todo 查看效果,同步更新到同名微信公众号(公众号搜索:flutter_todo)。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值