内容简介:(授权转载)微信码个蛋授权转载作者:这是我之前我预告要发的系列文章,昨天因为排版删了,没有原作者同意请勿二次转载。
(授权转载)微信码个蛋授权转载
作者:这是我之前我预告要发的系列文章,昨天因为排版删了,没有原作者同意请勿二次转载。
上一节撸了个界面,虽然比较简单,但是把前面讲的知识串联了下,但是界面之间的跳转一直没说,这节就讲下 Flutter
中的「路由」来管理界面。
Navigator
Flutter
通过 Navigator
来进行页面之间的跳转,分为 push
系列和 pop
系列操作,带 push
方法为入栈操作,带 pop
方法为出栈操作。 Navigator
的 push
方法分两类,一类是带 Name
的,需要在 MaterialApp
下将 routers
属性进行注册,否则将会找不到该路由,还有一个是不带 Name
的,可以通过 Router
直接跳转。
说那么多相信还不如直接上代码和图来的更直接。因为需要展示所有的跳转至少需要 3 个页面,所以我们创建最简单的三个界面,通过文字来区别不同的页面,因为需要调用带有 Name
的方法,所以需要先在 MaterialApp
对路由进行注册。
class DemoApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Learning Demo', // 在这里注册路由,关联 name 和界面 // '/' 表示根页面,也就是 home 所对应的页面,这边就不需要配置 home 属性了 routes: {'/': (_) => APage(), '/page_b': (_) => BPage(), '/page_c': (_) => CPage()}, debugShowCheckedModeBanner: false, ); } }/// Page A,Button 的跳转事件等会进行修改,目前先空着class APage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Page A'), ), body: Center(child: RaisedButton(onPressed: () {}, child: Text('To Page B'))), ); } }/// Page Bclass BPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Page B'), ), body: Center( child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ RaisedButton(onPressed: () {}, child: Text('To Page C')), RaisedButton(onPressed: () {}, child: Text('Back Page A')) ])), ); } }/// Page Cclass CPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Page C'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[RaisedButton(onPressed: () {}, child: Text('Back Last Page'))])), ); } }
push / pushNamed 方式跳转
我们在 APage
的 RaiseButton
的 onPressed
方法加入如下代码
Navigator.push(context, MaterialPageRoute(builder: (_) => BPage()));
或者
Navigator.pushNamed(context, '/page_b');
效果相同。跳转后,可以发现,在 BPage
的 AppBar
上有个返回按钮,点击可以返回 APage
,那么也就是说通过 push
或者 pushNamed
方式跳转的时候,界面堆栈的变化是直接在原来的堆栈上添加一个新的 page
为了凸显堆栈的变化,所以绘制的图中,会比使用的实际页面多一个,下图同
Navigator_push.png
pushReplacement / pushReplacementNamed / popAndPushNamed
将 APage
中的跳转方式进行替换
Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => BPage()));
或者
Navigator.pushReplacementNamed(context, '/page_b');
或者
// 如果是第一个界面跳转到下个界面,勿用,`BPage` 会显示返回按钮,但是点击后,界面会变黑// 因为 `APage` 已经不在堆栈中了,点击后堆栈就没有 `Page` 了,所以界面变黑Navigator.popAndPushNamed(context, '/page_b');
效果相同,跳转后,可以发现 BPage
的返回按钮消失了,消失了,消失了,我们可以试下点击返回按键,发现 App
直接退出了,也就是说, BPage
替代了 APage
在堆栈中的位置。那么堆栈的变化图就是这样的
Navigator_pushReplacement.png
pushAndRemoveUntil / pushNamedAndRemoveUntil
CASE 1
这个跳转方式需要通过 CPage
来协助完成,将 APage
的跳转方式修改为 push
方式,然后在 BPage
的第一个按钮加入如下代码
Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (_) => CPage()), (Route router) => false);
或者
Navigator.pushNamedAndRemoveUntil(context, '/page_c', (Route router) => false);
效果相同,点击 BPage
的跳转 CPage
按钮后,界面来到 CPage
,然后发现还是没有返回按钮,没有返回按钮,没有返回按钮,点击下返回按键,然后发现 App
直接退出了,退出了,退出了,那么堆栈变化如图
Navigator_pushAndRemoveUnit1.png
CASE 2
你以为这两个方法只是为了把堆栈都清空吗,那就太图样图森破了,这边展示另一种。修改跳转的代码
Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (_) => CPage()), ModalRoute.withName('/'));
或者
Navigator.pushNamedAndRemoveUntil(context, '/page_c', ModalRoute.withName('/'));
点击跳转 CPage
以后,发现返回按钮又回来了...就这么回来了...只是修改了一个参数,点击返回按钮,又回到了 APage
,你可以在 APage
跳转 BPage
中加入 DPage
EPage
等等更多的界面,只要保证 BPage
跳转 CPage
的方式不变,点击 CPage
的返回按钮,又回到 APage
了,所以...堆栈的变化图如下
Navigator_pushAndRemoveUnit2.png
SUMMARY
为什么会这样变化呢,还记得在 MaterialApp
中注册的 router
么, APage
的 name
对应的为 '/'
,也就是说,该方法会把堆栈中在 ModalRoute.withName
所对应的 page 上的所有都 pop 出堆栈,如果把参数换成 /page_b
,然后在跳转 CPage
之前加入更多的界面,点击 CPage
的返回按钮,就会回到 BPage
QUESTION
这边再提个小问题,有页面 A,B,C,D,其路由的 name 分别为 '/','page_b','page_c','page_d',启动顺序为 A -> B -> C -> C -> D,那么在 D 页面使用
Navigator.pushNamedAndRemoveUntil(context, '/page_c', ModalRoute.withName('/page_c'));
那么堆栈最后剩下的页面是 ABCC
还是 ABC
呢?答案会在最后公布,小伙伴可以先自己尝试着实现。
pop
在 BPage
的第二个按钮中加入 pop
操作
Navigator.pop(context);
跳转到 BPage
后点击该按钮,界面回到 APage
,那么堆栈的变化很明显了,如图
Navigator_pop.png
popUntil
这个方法还需要借助 CPage
,在 CPage
的按钮中加入
Navigator.popUntil(context, ModalRoute.withName('/'));
点击返回按钮,界面跳过 BPage
回到了 APage
,解释同 pushAndRemoveUntil
那么堆栈的变化也显而易见咯
Navigator_popUntil.png
Navigator 传值
CASE 1 传值给下个界面
修改下 BPage
和 APage
的按钮点击事件
class BPage extends StatelessWidget { final String message; BPage({Key key, @required this.message}) : super(key: key); @override Widget build(BuildContext context) { print('passed value: $message'); return Scaffold( // 省略相同代码 ); } }
// APage 跳转事件Navigator.push(context, MaterialPageRoute(builder: (_) => BPage(message: 'Message From Page A')));
点击 APage
可以查看控制台有输出
2019-03-17 00:04:06.854 12868-12888/com.kuky.demo.flutterartsdemosapp I/flutter: passed value: Message From Page A
也就是成功把值传递过来了。 但是,需要传递参数的话,之前在 MaterialApp
下注册的路由就需要去除了 。
CASE 2 传值给上个界面
这边可以查看下 pop
方法
@optionalTypeArgs // pop 可以传入一个可选参数 result,这个 result 也就是回传给上个页面的参数值了 static bool pop<T extends Object>(BuildContext context, [ T result ]) { return Navigator.of(context).pop<T>(result); }
既然知道 pop
如何传递值给上个界面,那么如何在上个界面接收这个参数呢,还是看下 push
方法
@optionalTypeArgs static Future<T> push<T extends Object>(BuildContext context, Route<T> route) { return Navigator.of(context).push(route); }///@optionalTypeArgs Future<T> push<T extends Object>(Route<T> route) { // ...省略无关代码 // 这边返回一个 Future 值,`pop` 所传递的值会在这边返回 return route.popped; }/// The future completes with the value given to [Navigator.pop], if any.Future<T> get popped => _popCompleter.future;
官方的注释非常明白的指出,会在 Future
中携带 pop
传递的参数,那么我们对 APage
跳转 BPage
以及 BPage
返回 APage
的逻辑进行修改
/// APageNavigator.push(context, MaterialPageRoute(builder: (_) => BPage(message: 'Message From Page A'))) .then((value) => print('BACK MESSAGE => $value'));
/// BPageNavigator.pop(context, 'Message back to PageA From BPage');
点击返回后,能够在控制台发现有如下输入
2019-03-17 16:35:53.820 13417-13442/com.kuky.demo.flutterartsdemosapp I/flutter: BACK MESSAGE => Message back to PageA From BPage
上个页面成功接收到下个页面回传的数据。
CASE 3 通过系统返回按钮传值
在 CASE 2
情况下,通过按钮对返回事件进行监听,那加入我们需求没有这个按钮,只能通过系统默认的返回按钮,或者物理返回按键,那该如何传值呢,这里就需要用 WillpopScope
对系统的返回按钮进行监听。我们对 CPage
做下修改,在 Scaffold
外面包裹一个 WillpopScope
class CPage extends StatelessWidget { @override Widget build(BuildContext context) { return WillPopScope( child: Scaffold( appBar: AppBar( title: Text('Page C'), ), body: Center( child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ RaisedButton( onPressed: () { Navigator.popUntil(context, ModalRoute.withName('/')); }, child: Text('Back Last Page')) ])), ), // 这里对系统返回按钮做监听.. // 如果返回的是 `true` 则相当于 `pop` 操作,返回 `false` 则只执行上一步的 `pop` 操作 // 例如双击返回退出,也是通过 `WillpopScope` 来进行监听 onWillPop: () async { Navigator.pop(context, 'Hello~'); return false; }); } }
通过返回按钮, BPage
会成功收到从 CPage
返回的 Hello~
值
以上代码查看 router_main.dart
文件
路由切换动画
假如说我们不想用系统自带的切换动画,需要弄一些比较酷炫的效果该怎么办,那就需要用到自定义路由切换动画了。直接修改 BPage
跳转 CPage
的代码
Navigator.push( context, PageRouteBuilder( // 返回目标页面 pageBuilder: (context, anim, _) => CPage(), // 切换动画的切换时长 transitionDuration: Duration(milliseconds: 500), // 切换动画的切换效果,系统自带的常用 Transition // ScaleTransition: 缩放 SlideTransition: 滑动 // RotationTransition: 旋转 FadeTransition: 透明度 transitionsBuilder: (context, anim, _, child) => ScaleTransition( // Tween 是 flutter 的补间动画,等讲到动画的时候再提吧,这边先记住这么使用 scale: Tween(begin: 0.0, end: 1.0).animate(anim), // 这个值必须记得要传,否则会不显示界面 child: child, )));
当再次点击跳转的时候,切换的动画就有开始自带的平滑效果变成缩放效果了。那如果要实现多个动画呢,例如边缩放,边改变透明度,也很容易实现,只需要将 child
替换成 Transition
即可
Navigator.push( context, PageRouteBuilder( pageBuilder: (context, anim, _) => CPage(), transitionDuration: Duration(milliseconds: 500), transitionsBuilder: (context, anim, _, child) => ScaleTransition( scale: Tween(begin: 0.0, end: 1.0).animate(anim), // 替换即可,如果要加入更多的动画,替换 `child` 属性就可以了 child: FadeTransition( opacity: Tween(begin: 0.0, end: 1.0).animate(anim), child: child, ), )));
当然,为了方便重复利用,需要进行封装,例如我们要封装上面的缩放动画效果
class ScalePageRoute extends PageRouteBuilder { final Widget widget; ScalePageRoute(this.widget) : super( transitionDuration: Duration(milliseconds: 500), pageBuilder: (context, anim, _) => widget, transitionsBuilder: (context, anim, _, child) => ScaleTransition( scale: Tween(begin: 0.0, end: 1.0).animate(anim), child: child, )); }
然后直接在 Navigator
跳转的时候调用该 Route
就可以了
该部分代码查看 custom_routes.dart
文件
还记得我们之前写的 demo
都是单个文件写一个入口的吗,现在我们就可以写一个统一管理的页面,对这些界面进行管理了,这个工作就交给大家伙自己了,当然我也在源码做了修改,可以查看 main.dart
文件
在前面有提出一个问题,这边公布下答案:堆栈中的页面应该为 ABCC
。你答对没有呢~
最后代码的地址还是要的:
-
文章中涉及的代码: demos
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Istio 服务网格路由入门
- 路由器漏洞挖掘之栈溢出入门(二)
- Golang Web入门(2):如何实现一个高性能的路由
- 【CuteJavaScript】Angular6入门项目(1.构建项目和创建路由)
- Akka入门系列(六):akka cluster中的路由和负载均衡
- 路由器漏洞挖掘之栈溢出入门(三)ROP链的构造
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。