Flutter 路由管理 Route、Navigator 使用示例
文章目录
路由管理
在
Flutter
中,页面之间的跳转是通过Route
和Navigator
来管理。
Router
是页面的抽象,类似于Android
中的Activity
页面。该类定义了Navigator
上的抽象接口push 和 pop
。
而Navigator
是用堆栈规则管理一组child widget
的widget
。可以理解为管理页面的打开、关闭
。
页面跳转示例
页面不传参跳转
- 不传值跳转
/// 从当前页面跳转到 SecondPage
Navigator.push(context, MaterialPageRoute(builder: (context) => SecondPage()));
- 返回不传值
Navigator.pop(context);
页面传参跳转
- 传值跳转
/// 从当前页面跳转到 SecondPage,并通过构造函数传递参数的方式传递了 hello 字符串
Navigator.push(context, MaterialPageRoute(builder: (context) => SecondPage('hello')));
- 返回传值
/// 第一个页面跳转处。利用 then 接收 pop 携带回来的参数
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return SecondPage('hello');
})).then((value) {
/// value 就是从第二个页面传递回来的值
setState(() {
mFirstPageMsg = value;
});
});
/// 第二个页面,pop 参数
Navigator.pop(context, 'From SecondPage');
Navigator 的其他跳转方式
我们在调用Navigator
方法的时候可以看到除了以上push , pop
方法之后还有其他的方法.
获取Navigator实例:
of
获取Navigator
最近的实例
/// 典型示例:关闭两个页面后跳转到 settings 页面
Navigator.of(context)
..pop()
..pop()
..pushNamed('/settings');
页面跳转:
push
页面跳转pushNamed
使用命名路由跳转页面。
/// 典型示例:页面跳转
class WeatherRouteArguments {
WeatherRouteArguments({ this.city, this.country });
final String city;
final String country;
bool get isGermanCapital {
return country == 'Germany' && city == 'Berlin';
}
}
void _showWeather() {
Navigator.pushNamed(
context,
'/weather',
arguments: WeatherRouteArguments(city: 'Berlin', country: 'Germany'),
);
}
pushAndRemoveUntil
跳转到新的页面,并删除先前的所有路由,常用于退出登录后返回登录页面,这时候关闭登录页面就直接退出App
。
/// 典型示例:关闭其他页面,回到首页
void _finishAccountCreation() {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (BuildContext context) => MyHomePage()),
ModalRoute.withName('/'),
);
}
pushNamedAndRemoveUntil
同上,不过这里是使用命名路由
/// 典型示例:关闭其他页面,回到日历页面
void _resetToCalendar() {
Navigator.pushNamedAndRemoveUntil(context, '/calendar', ModalRoute.withName('/'));
}
pushReplacement
路由替换。
/// 典型示例:完成登录后回到首页
void _completeLogin() {
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (BuildContext context) => MyHomePage()));
}
pushReplacementNamed
路由替换。跳转到新页面且动画关闭之后之后,调用dispose
关闭上一个页面.
/// 典型示例:跳转到 brightness 页面且动画关闭之后之后,调用 disposing 关闭上一个页面
void _switchToBrightness() {
Navigator.pushReplacementNamed(context, '/settings/brightness');
}
页面关闭:
pop
页面关闭canPop
判断是否可以导航到新页面maybePop
可能会导航到新页面popAndPushNamed
关闭当前页面,并导航到新页面。
/// 典型示例:关闭当前页面并用 accessibility 页面替换
void _selectAccessibility() {
Navigator.popAndPushNamed(context, '/settings/accessibility');
}
popUntil
页面关闭,反复调用pop
方法直到该函数的参数predicate
返回true
为止
/// 典型示例如下
void _logout() {
Navigator.popUntil(context, ModalRoute.withName('/login'));
}
页面移除:
removeRoute
页面删除,并执行Route.dispose
。removeRouteBelow
页面删除,同时执行Route.dispose
操作,要移除的页面是参数anchorRouter
里面的路由。
页面替换:
replace
页面替换,参数中填写新、旧路由。replaceRouteBelow
页面替换,要替换的页面是是参数anchorRouter
里面的路由。
无 context 页面跳转
有时候页面跳转是没有
context
。
这里使用GlobalKey
的方式实现:
- 首先创建
NavigationService
类,并创建static
的navigatorKey
实例,后续的页面跳转方法也可封装在这个类中
/// 无 context 跳转页面
class NavigationService {
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
/// push 新页面
static Future<T> navigateTo<T>(Route<T> route) {
return navigatorKey.currentState.push<T>(route);
}
/// pop 页面
static bool goBack() {
return navigatorKey.currentState.pop();
}
}
- 然后在
main.dart
中的MaterialApp
下设置全局的navigatorKey
,如下图:
- 最后就是调用方法实现页面跳转了
/// 调用已经封装好的方法跳转 ThirdPage 页面
NavigationService.navigateTo(MaterialPageRoute(builder: (context) => ThridPage('word')));
命名路由
直接使用
Navigator push
进行页面跳转是相当简单的,但是简单的同时也会带来维护的复杂。在页面很多的情况下,每个页面页面跳转都在不同的代码中,而且每次页面跳转都要创建MaterialPageRoute
实例,还是比较麻烦的。我们在上面的Navigator
中也看到一些命名路由的方式实现页面跳转。
那么我们来看一下命名路由如何使用吧:
1. 在 main.dart 的 MaterialApp 中注册路由
MaterialApp(
...
//注册路由
routes:{
/// 第三个页面的路由
"third_page":(context)=>ThridPage(),
},
);
2. 使用路由
//使用名字打开页面
Navigator.pushNamed(context,"third_page");
页面跳转传参
//打开页面时传递字符串参数
Navigator.of(context).pushNamed("second_page", arguments: "Hey");
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
//取出路由参数
String msg = ModalRoute.of(context).settings.arguments as String;
return Text(msg);
}
}
页面返回传参
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Text('Message from first screen: $msg'),
RaisedButton(
child: Text('back'),
//页面关闭时传递参数
onPressed: ()=> Navigator.pop(context,"Hi")
)
]
));
}
}
class _FirstPageState extends State<FirstPage> {
String _msg='';
@override
Widget build(BuildContext context) {
return new Scaffold(
body: Column(children: <Widget>[
RaisedButton(
child: Text('命名路由(参数&回调)'),
//打开页面,并监听页面关闭时传递的参数
onPressed: ()=> Navigator.pushNamed(context, "third_page",arguments: "Hey").then((msg)=>setState(()=>_msg=msg)),
),
Text('Message from Second screen: $_msg'),
],),
);
}
}
命名路由封装
上面这么使用完成了命名路由。但是当页面过多,在routers
中的路由就会很多,就会导致main.dart
入口文件臃肿。这时候可以考虑将路由封装出去。
这里参考的是Flutter-Go
中的实现方式:
- 首先定义
Routers
类
class Routes {
...
/// 路由名称
static String thirdPage = '/third-page';
/// 配置路由名称和 handler(参数)
static void configureRoutes(Router router) {
...
// 第三页
router.define(thirdPage,handler:thirdHandler);
}
}
- 定义
Handler
,处理传递的参数
这里将所有handler
统一放到router_handler.dart
文件下:
/// 第三个页面的 handler
var thirdHandler = new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
/// 传递的 msg 参数,这里的参数变量最好是定义为常量,免得修改不同步。
String msg = params['msg']?.first;
/// 其他参数获取也是一样
/// String title = params['title']?.first;
return new ThridPage(msg);
},
);
- 接着在
main.dart
文件中定义,如下:
- 最后调用就比较简单了
// 第二个参数后面拼接路由名称和传递的参数
Navigator.pushNamed(context,Routes.thirdPage+'?msg=word');
// 多个参数传递
// Navigator.pushNamed(context,Routes.thirdPage+'?msg=word&title=hello');
404 页面处理
由于路由的注册和使用都采用字符串来标识,如果我们打开了一个不存在,默认是会在控制台提示,但是app上没有任何反应。这时候体验就不是很好了。
Flutter
提供了UnknownRoute
属性,我们可以对未知的路由标识符进行统一的页面跳转处理。
使用方式也很简单:
- 创建一个 404 页面:
/// 页面路由出错显示的页面
class PageNotFound extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("page not found"),
),
body: Container(
child: Text("page not found")
)
);
}
}
- 在
main.dart
中引用
返回按钮拦截
WillPopScope,点击两次返回才退出,点击返回出现提示框提示退出
实现可参考:Flutter-WillPopScope-双击返回与界面退出提示
wan~