flutter - 路由
前言
flutter 中的页面管理由 Route 和 Navigator 两个核心概念,Route 代表着一个页面的抽象,而 Navigator 则是管理 Route 的 Widget。Navigator可以通过route入栈和出栈来实现页面之间的跳转。
Navigator
Navigator 作为一个管理路由的控件,继承自 StatefulWidget,通过 NavigatorState 来保存路由栈,负责页面的出入栈,而 MaterialApp 和 WidgetApp 作为必须使用的根 widget 已经在内部集成了 Navigator,所以可以在任意地方都可以通过 Navigator.of(context) 方法通过上下文环境来获取 NavigatorState 实例来操作页面路由,具体的方法也不一一介绍了,这里有几个主要的变量值得介绍一下
1 | /// Route 列表,先入后出的路由栈 |
我们可以通过 NavigatorState 的 build 函数来简单的看一下路由器的构成
1 |
|
Navigator 会在页面变换时接管触摸事件的拦截取消工作,会生成一个默认的焦点,以及通过 Overlay 最终展示页面层
Route
Route 是一个抽象类,是作为 Navigator 管理路由的单元存在的,这个抽象类定义了一些抽象方法,包括生成用于展示的一个或者多个 OverlayEntry。我们常熟悉的 Route 就是MaterialPageRoute 了,下面是 MaterialPageRoute 的继承图
1 | MaterialPageRoute |
下面也会以 MaterialPageRoute 为例,详细地分析一下入栈弹出页面时的操作
页面入栈 Navigator.of(context).push(route)
1 |
|
route.install 方法会准备一些前置操作,比如说在 TransitionRoute 会准备一些 AnimationController 的初始化工作,在 ModalRoute 中会对 AnimationController 进行一定的包装,用于两个页面的转换的动画交互,而在 OverlayRoute 中,则会把 Route 生成的页面即 OverlayEntry 插入到 NavigatorState 的 Overlay 控件中
1 | @override |
route.didPush() 则开始转场动画,主要在 TransitionRoute.didPush 方法中开始,还有比较重要的一点是会为动画设置监听器
1 | void _handleStatusChanged(AnimationStatus status) { |
监听器的目的就是在转场动画播放时将最上面的一层设置为透明,就可以展现完整的动画效果了
1 | if (oldRoute != null) { |
AnimationController 在前面就已经被触发了,
接着的这段代码就是让被覆盖的页面执行一些转场动画,比如说我们熟知的 ModalRoute 中 secondaryAnimation 会通过 TrainHoppingAnimation 这种代理器去关联前面提到的会在 didPush 中运行的 AnimationController,这样就能同步执行前一个页面的退出动画和新页面的进入动画,这些代码可以在 TransitionRoute 中找到,这就是我们通过 PageRouteBuilder 实现的转场动画原理
最后就是去通知一些注册的路由观察者,以及 _afterNavigation 去处理一些后续的事情,比如说取消前一个页面的触摸事件,最后就是返回一个 future
Navigator.of(context).pop()
如果理解了路由加载,那么弹出路由就比较容易理解了
1 | @optionalTypeArgs |
通过 route.didPop(result ?? route.currentResult) 将值传递回去,TransitionRoute.didPop 中会反向执行动画控制器,OverlayRoute 则会做一些特殊流程的处理工作
根节点是不会被移除的。接着被移除的 route 会被加入到 _poppedRoutes 做统一处理。 _history.last.didPopNext(route) 这里流程和 pop 时一样会执行被返回页面的相关动画。最后就是处理跳转后的一些收尾工作。
原生操作的监听
对于 Android 来说原生系统是由返回键的,那 flutter 是如何将原生事件和 Navigator 关联起来的?
widget 层和 flutter 引擎之间有一层胶水层 WidgetsBinding 负责两层之间的数据交互,在 initInstances 初始化方法中通过下列方法订阅了原生系统的导航事件
1 | SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation); |
WidgetBinding._handleNavigationInvocation
1 | Future<dynamic> _handleNavigationInvocation(MethodCall methodCall) { |
原生会通过 popRoute 和 pushRoute 两个方法控制路由跳转,以 handlePopRoute 为例
1 | @protected |
如果没有对象处理就会交由原生去处理,我们知道 flutter 应用必须要有 WidgetsApp 作为根 widget,而 _WidgetsAppState 实现了 WidgetsBindingObserver 并在 initState 方法中注册自己到 WidgetBinding 中
_WidgetsAppState.didPopRoute
1 | @override |
最终会关联到 navigator.maybePop 方法上
1 | @optionalTypeArgs |
Route.willPop
1 | Future<RoutePopDisposition> willPop() async { |
只要 route 不是跟页面就会执行 Navigator.pop 了,而页面添加也是类似的流程
结语
Navigator 当然还有许多的其他相关方法,比如说 pushReplacement 等,但是万变不离其宗,只要了解了 push 和 pop 的流程其他的也就比较容易了解。flutter 的这一套页面路由实现充满了灵活性,让页面间的转场动画实现以及本身的管理变得更加容易,相较于 Android 涉及到 AMS,WMS 等的 Activity 跳转无疑是简单多了,而且也预留了监听接口更是方便的埋点之类额外功能的开发工作。