所谓“命名路由”(Named Route)即有名字的路由,我们可以先给路由起一个名字,然后就可以通过路由名字直接打开新的路由了,这为路由管理带来了一种直观、简单的方式。
路由匹配规则
如果 initialRoute 设置为 /,那么加载 HomePage 页面;
如果 initialRoute 设置为 /search,在routes中存在,就加载routes中指定的路由,即SearchPage页面;
如果 initialRoute 设置为 /bag,此时routes中并不存在名称为 /bag 路由,调用onGenerateRoute;
如果 onGenerateRoute 返回路由页面,则加载此页面;
如果返回的是null,且 home参数 不为null,则加载home参数指定的页面;
如果 home 为null,则回调onUnknownRoute。
路由表
要想使用命名路由,我们必须先提供并注册一个路由表(routing table),这样应用程序才知道哪个名字与哪个路由组件相对应。其实注册路由表就是给路由起名字,路由表的定义如下:
final Map<String, WidgetBuilder> routes;
它是一个Map,key为路由的名字,是个字符串;value是个builder回调函数,用于生成相应的路由widget。我们在通过路由名字打开新路由时,应用会根据路由名字在路由表中查找到对应的WidgetBuilder回调函数,然后调用该回调函数生成路由widget并返回。
路由表的注册方式很简单,在MyApp类的build方法中找到MaterialApp,添加routes属性,代码如下:
import "package:flutter/material.dart";import "pages/tabs/Tabs.dart";import "pages/form/Form.dart";import "pages/search/Search.dart";void main() => runApp(MyApp());class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(//默认路由initialRoute: '/test',//注册路由表routes: {"/": (context) => HomePage(),"/test": (context) => TestPage(),"/search": (context) => SearchPage({'q': 'asdf'}),//"/search": (context) => SearchPage( ModalRoute.of(context).settings.arguments ),},);}}
Navigator
pushNamed 跳转
//定义static Future<T> pushNamed<T extends Object>(BuildContext context,String routeName, {Object arguments,}) {return Navigator.of(context).pushNamed<T>(routeName, arguments: arguments);}//跳转Navigator.pushNamed(context,'/search',arguments: { //传参'id': 1,},).then((value) {print('回传参数 $value'); //回传参数 {a: 1}});//接受参数final arg = ModalRoute.of(context).settings.arguments;print('arg $arg'); //args => {id: 1}
pushReplacementNamed 替换
//定义static Future<T> pushReplacementNamed<T extends Object, TO extends Object>(BuildContext context,String routeName, {TO result,Object arguments,}) {return Navigator.of(context).pushReplacementNamed<T, TO>(routeName, arguments: arguments, result: result);}//跳转Navigator.pushReplacementNamed(context,'/search',);
pushNamedAndRemoveUntil 先跳转再删除先前路由
先将命名路由推送到Navigator,再删除先前的路由,一直到该函数的参数 predicate 返回true为止。
如果 predicate 一直返回false,则会清空先前的所有路由。
//定义static Future<T> pushNamedAndRemoveUntil<T extends Object>(BuildContext context,String newRouteName,RoutePredicate predicate, {Object arguments,}) {return Navigator.of(context).pushNamedAndRemoveUntil<T>(newRouteName, predicate, arguments: arguments);}//Navigator.pushNamedAndRemoveUntil(context,'/search',(route) => false,);
popAndPushNamed 先后退再跳转
指定一个路由路径,先退出再导航到新页面。需要注意与 pushReplacement 的区别。
pushReplacement 进入 -> 销毁 popAndPushNamed 退出 -> 销毁 -> 进入
//定义static Future<T> popAndPushNamed<T extends Object, TO extends Object>(BuildContext context,String routeName, {TO result,Object arguments,}) {return Navigator.of(context).popAndPushNamed<T, TO>(routeName, arguments: arguments, result: result);}//Navigator.popAndPushNamed(context,'/search',);
参数传递
https://flutter.axuer.com/docs/cookbook/navigation/navigate-with-arguments.html
通过 RouteSettings/ModalRoute 传参
在Flutter最初的版本中,命名路由是不能传递参数的,后来才支持了参数;下面展示命名路由如何传递并获取路由参数:
我们先注册路由至路由表:
routes:{"/test":(context) => TestPage(),"/search":(context) => SearchPage(),} ,
在跳转路由时传递参数
Navigator.pushNamed(context,'/search',arguments: { //传参'id': 1,},)
在目标路由页通过RouteSetting对象获取路由参数:
//search页面接收参数final arg = ModalRoute.of(context).settings.arguments;print('arg $arg'); //args => {id: 1}
传参适配路由表
上面我们是通过 ModalRoute 获取参数的,那么我们如何通过构造函数来获取参数呢?
答:在路由表中通过 ModalRoute 获取到参数后,再把参数传递给key对应的路由页面组件。
路由表
routes: {"/": (context) => HomePage(),"/test": (context) => TestPage(),// "/search": (context) => SearchPage(),"/search": (context) => SearchPage(ModalRoute.of(context).settings.arguments),},
跳转
Navigator.pushNamed(context,'/search',arguments: {'id': 1,},)
search页面接收
import "package:flutter/material.dart";class SearchPage extends StatelessWidget {SearchPage(this.args);final args;@overrideWidget build(BuildContext context) {print('search => ${widget.args}'); //search => {id: 1}final arg = ModalRoute.of(context).settings.arguments;print('arg $arg'); //args => {id: 1}return null;}}
onGenerateRoute 路由生成钩子
假设我们要开发一个电商APP,当用户没有登录时可以看店铺、商品等信息,但交易记录、购物车、用户个人信息等页面需要登录后才能看。为了实现上述功能,我们需要在打开每一个路由页前判断用户登录状态!如果每次打开路由前我们都需要去判断一下将会非常麻烦,那有什么更好的办法吗?答案是有!
MaterialApp有一个onGenerateRoute属性,它在打开命名路由时可能会被调用,之所以说可能,是因为当调用Navigator.pushNamed(...)打开命名路由时,如果指定的路由名在路由表中已注册,则会调用路由表中的builder函数来生成路由组件;如果路由表中没有注册,才会调用onGenerateRoute来生成路由。onGenerateRoute回调签名如下:
Route<dynamic> Function(RouteSettings settings)
有了onGenerateRoute回调,要实现上面控制页面权限的功能就非常容易:我们放弃使用路由表,取而代之的是提供一个onGenerateRoute回调,然后在该回调中进行统一的权限控制,如:
MaterialApp(initialRoute: '/test', //默认路由onGenerateRoute:(RouteSettings settings){return MaterialPageRoute(builder: (context){String routeName = settings.name;// 如果访问的路由页需要登录,但当前未登录,则直接返回登录页路由,// 引导用户登录;其它情况则正常打开路由。});});
注意,
onGenerateRoute只会对命名路由生效。
简单封装
main.dart
import "package:flutter/material.dart";import "package:app1/routes/RoutesOne.dart";void main() => runApp(MyApp());class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(initialRoute: '/', //默认路由onGenerateRoute: RoutesOne,);}}
routes/RoutesOne.dart
import "package:flutter/material.dart";import 'package:app1/pages/home/index.dart';import 'package:app1/pages/test/index.dart';import 'package:app1/pages/search/index.dart';import 'package:app1/pages/bag/index.dart';final Map<String, Function> myRoutes = {"/": (context) => HomePage(),"/test": (context) => TestPage(),"/search": (context, {arguments}) => SearchPage(arguments),"/bag": (context, {arguments}) => BagPage(id: arguments.id),};Function RoutesOne = (RouteSettings settings) {final String name = settings.name;final Function myRoutesBuilder = myRoutes[name];return MaterialPageRoute(builder: (context) {if (myRoutesBuilder == null) {return Text('未发现路由页面');} else {if (settings.arguments == null) {return myRoutesBuilder(context);} else {return myRoutesBuilder(context, arguments: settings.arguments);}}},);};
A页面
Navigator.pushNamed(context,'/search',arguments: {'id': 1,},)
search页面
import "package:flutter/material.dart";class SearchPage extends StatelessWidget {SearchPage(this.args);final args;@overrideWidget build(BuildContext context) {print('search => ${widget.args}'); //search => {id: 1}return null;}}
总结
本章先介绍了Flutter中路由管理、传参的方式,然后又着重介绍了命名路由相关内容。在此需要说明一点,由于命名路由只是一种可选的路由管理方式,在实际开发中,可能心中会犹豫到底使用哪种路由管理方式。在此,建议最好统一使用命名路由的管理方式,这将会带来如下好处:
- 语义化更明确。
- 代码更好维护;如果使用匿名路由,则必须在调用
Navigator.push的地方创建新路由页,这样不仅需要import新路由页的dart文件,而且这样的代码将会非常分散。 - 可以通过
onGenerateRoute做一些全局的路由跳转前置处理逻辑。
综上所述,比较建议使用命名路由,可以根据自己偏好或实际情况来决定。
另外,比如路由MaterialApp中还有navigatorObservers和onUnknownRoute两个回调属性,前者可以监听所有路由跳转动作,后者在打开一个不存在的命名路由时会被调用,由于这些功能并不常用,用到时可以自行查看API文档
