所谓“命名路由”(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 {
@override
Widget 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;
@override
Widget 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 {
@override
Widget 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;
@override
Widget build(BuildContext context) {
print('search => ${widget.args}'); //search => {id: 1}
return null;
}
}
总结
本章先介绍了Flutter中路由管理、传参的方式,然后又着重介绍了命名路由相关内容。在此需要说明一点,由于命名路由只是一种可选的路由管理方式,在实际开发中,可能心中会犹豫到底使用哪种路由管理方式。在此,建议最好统一使用命名路由的管理方式,这将会带来如下好处:
- 语义化更明确。
- 代码更好维护;如果使用匿名路由,则必须在调用
Navigator.push
的地方创建新路由页,这样不仅需要import新路由页的dart文件,而且这样的代码将会非常分散。 - 可以通过
onGenerateRoute
做一些全局的路由跳转前置处理逻辑。
综上所述,比较建议使用命名路由,可以根据自己偏好或实际情况来决定。
另外,比如路由MaterialApp中还有navigatorObservers
和onUnknownRoute
两个回调属性,前者可以监听所有路由跳转动作,后者在打开一个不存在的命名路由时会被调用,由于这些功能并不常用,用到时可以自行查看API文档