相关阅读
基于 Navigator1.0 :
- Flutter 法法路由注解
- Flutter 法法注解路由 2.0
- Flutter 法法注解路由 4.0
前言
Flutter 1.22 发布带来了 Navigator2.0 , 给了开发者更多选择,你可以灵活地管理路由栈,可以处理在浏览器里面输入的情况,也可以嵌套多个 Navigator,虽然仍然有缺点,但是基本上可以做到 路由我定
。下面跟我一起走进 Flutter 路由的世界。本文源码版本为 Flutter Stable 1.22.6。
路由基础
Navigator
负责整个路由栈, 在结构上面其实是一个 Overlay 有点类似 Stack
,大家经常用它来做 toast
,比如 oktoast,其实每个页面就是一个 OverlayEntry
.
RouteSettings
用来保存路由名字和参数
Page
Navigator2.0 中出现,继承于 RouteSettings
。主要负责自己创建 Route
以及提一个 key,这个 key 是后面 Page
变化判断的依据,注意这个 key 在通常情况下应该为唯一的 key 。
Route
主要负责处理跳转的动画,保存 RouteSettings
(即路由的名字和参数)。也是 OverlayEntry,Navigator,_RouteEntry 的纽带。
- 在
push
方法中,Route
直接传递进来,赋值给_RouteEntry
增加到_history
中
NavigatorState.build
中 Overlay 的 initialEntries 的值等于 _history 中全部 _RouteEntry.route 的 OverlayEntry
RouteTransitionRecord / _RouteEntry
后者继承前者,记录每个路由的状态
RouteTransitionRecord
中有以下属性和方法,它们都在 TransitionDelegate.resolve
方法中设置
isWaitingForEnteringDecision
标记路由是否等待进入屏幕
isWaitingForExitingDecision
标记路由是否等待离开屏幕
方法 | 进出 | 动画 | 返回参数 | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
markForPush() | 进 | 有 | N/A | markForAdd() | 进 | 无 | N/A | markForPop([dynamic result]) | 出 | 有 | 有 | markForComplete([dynamic result]) | 出 | 无 | 有 | markForRemove() | 出 | 无 | 无 |
TransitionDelegate / DefaultTransitionDelegate
RouteTransitionRecord
在 TransitionDelegate.resolve
中进行设置.
1.当 Pages
变化的时候触发更新,执行了 NavigatorState.didUpdateWidget
2.在这个方法中,去对比了新旧 Pages
(我们前面说的 K 新增了一个 key,就在这里发挥了作用)
3.resolve
中判断哪些是新增,哪些是要移除的。
DefaultTransitionDelegate 中的大概逻辑如下
新Pages | 旧Pages | 状态 | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
A=>B | A=>B=>C | C markForPop | A=>C | A=>B=>C | B markForComplete | A=>B=>C=>D | A=>B=>C | D markForPush | A=>B=>D=>C | A=>B=>C | D markForAdd |
NavigatorObserver
用于监控 push,pop,replace,remove 路由的情况,以及 ios 平台上面左滑退出页面。通常我们可以在这个里面做页面进出埋点,以及解决混合开发中 Flutter与原生 ios 左滑退出冲突。
Navigator 1.0
这个大家都用了很久了,相信都是非常了解的了,推荐使用命名路由,并且在 onGenerateRoute
回调中统一管理路由。之前法法路由注解也是基于 Navigator 1.0
的,不懂的可以去回顾下。
- Flutter 法法路由注解
- Flutter 法法注解路由 2.0
- Flutter 法法注解路由 4.0
Navigator 2.0
先看一下 Navigator 的最新构造有了一些什么变化。
Navigator 栗子
- 我们准备一下
Page
,简单的实现下createRoute
方法
- 准备
pages
,这就是我们的路由栈,初始化的一个 MainPage。要注意的是,key 必须是一个 唯一的 key。
- 使用 Navigator,值得注意的是
pages
应该是一个新的集合,这样在NavigatorState.didUpdateWidget
中才会判断不同,并且更新onPopPage
回调可以根据自身的情况,进行操作,最后setState
,通知pages
变化。通过调用navigatorKey.currentState.pop()
或者 点击 Appbar 返回按钮都会触发该回调
- 现在你就可以任意去操作路由栈了,下面举几个例子。
新增一个页面,相当于 push
移除最后一个,相当于 pop
直接使用 NavigatorState.pop()
方法,触发 onPopPage
回调
- 完整 demo 地址
现在看起来,我们能够完美控制整个路由栈了,是不是就够了呢? 答案肯定是不够的,我们还没有处理 浏览器输入修改URL
, 浏览器返回键
,安卓物理返回键
,以及 Navigator 嵌套
的问题。
Router
Navigator 2.0 的新东西,一眼看过去全是新东西。
RouteInformation
存在下面2种场景:
- RouteInformationProvider => Router , 这种情况发生在有新的路由可用,比如在浏览器中输入一个新URL,或者在代码设置初始化路由。
- Router => RouteInformationProvider, 这种情况只发生在通知引擎改变浏览器 URL。
RouteInformationParser
主要负责解析 RouteInformation
,这里的 T
一般为 String
或者 RouteSettings
,方便我们进行解析。
RouteInformationProvider
主要负责通知 RouteInformation 变化
常用它初始化路由
RouterDelegate
创建和配置 Navigator
的代理,跟之前写的 Navigator demo 差不多。不同的是增加对 浏览器输入修改URL
, 浏览器返回键
,安卓物理返回键
的处理。
PopNavigatorRouterDelegateMixin
帮你实现了 RouterDelegate 中的 popRoute
方法,不是必须的。
源码分析
前面只是讲了哪些 api 解决了哪些问题,这里我们来追踪一下官方是如何实现的,我在浏览器里面输入一个新的URL。
-
从图上最后一步,很明显看来这是一个从引擎过来的原生方法
-
到了_handleNavigationInvocation 方法,
pop
和push
都在这里了,这里就是接收来之引擎的通知包括 浏览器输入,浏览器和安卓物理返回按钮点击。
- 在方法里面,分别给注册过的
WidgetsBindingObserver
分发事件,当发现处理之后,return 掉。(这里预埋了嵌套Navigator
的坑)
- WidgetsApp 继承了 WidgetsBindingObserver,在
WidgetsBinding.instance.addObserver(this)
中把自己加入_observers
里面
- 而
PlatformRouteInformationProvider
在Router 初始化的时候 addListener的时候把自己加到_observers
中。
最终我们就能获取到引擎告诉我们的路由变化了!
BackButtonDispatcher
包括 RootBackButtonDispatcher
和 ChildBackButtonDispatcher
。
主要是为了解决 Navigator 嵌套
,返回按钮优先级的问题。
举个栗子:
1.MyApp
是一个 Navigator
, 初始页面为 NestedMainPage
,中间一个按钮点击之后 push
到 ChildRouterPage
。 现在 第一个 Navigator
中有 2个页面(NestedMainPage
,ChildRouterPage
)
2.ChildRouterPage
也是一个 Navigator
,初始页面为 NestedTestPage
,中间一个按钮点击之后 push
到 TestPageA
。 现在 第一个 Navigator
中有 2个页面(NestedTestPage
,TestPageA
)
3.现在我们可以看到的是 TestPageA
, 那么现在按 安卓物理返回键
或者 浏览器返回键
,是什么现象?
4.页面退回到了 NestedMainPage
,这一定不是大家想要的结果吧?
那么我们怎么解决这个问题呢?前面我们知道我们是有办法监听 安卓物理返回键
或者 浏览器返回键
的,也就是 _handleNavigationInvocation
方法中的 popRoute
回调,但是优先处理的第一个能够 pop
的 Navigator
(还记得分发的时候那个坑吗)。实际上我们可以自己来决定 popRoute
回调作用于哪个一个 Navigator
。我们只需要在第2个 Router 这里做以下操作。
获取到上一个 Router
中 backButtonDispatcher
,并且获取优先级。
源码分析:
- 很熟悉的东西
WidgetsBindingObserver
,addCallback
的时候把自己加到监听_observers
,等待引擎传递事件,WidgetsBinding
中分发。
- 在调用
takePriority
方法时候讲自己加到parent
的children
当中,确保自己是最后一个,并且清空了自己的children
。
- 在
invokeCallback
方法,从children
最后一个开始遍历(这就是为啥在deferTo
方法中先remove
,后add
),看谁 handle 了didPopRoute
事件,如果处理了就停止。
-
因为在
Router
里面有增加对BackButtonDispatcher
的监听( 源码中位置),所以最终会通知到RouterDelegate.popRoute
。 -
完整 demo 地址
Navigator 2.0 总结
- 通过对
Navigator.pages
的管理,实现对路由栈的完全掌握。 - 通过
Router
以及相关的Api
解决浏览器输入修改URL
,浏览器返回键
,安卓物理返回键
与原生交互的问题,和对Navigator
代理和配置。 - 通过
BackButtonDispatcher
处理了Navigator 嵌套
的问题。
看起来,Navigator 2.0 肥肠完美了,但是实际使用也还是存在一些缺点。
- 要实现的东西有点多,难道不能
duang
一下就能用吗? - Web 浏览器中手动输入参数解析的问题
- 由于
Route
在Page
的createRoute
方法中生成,导致我们没法直接访问到Route
。如果我们要写一个类似push
有回调参数的方法该怎么办呢?
嗯,是的,法法路由注解 5.0
已经完美支持 Navigator 1.0
和 2.0
了,散花!
法法路由注解 5.0
增加引用
添加引用到dependencies
,及你需要注解的 project/packages 到pubspec.yaml
中
执行 flutter packages get
下载
添加注解
空构造
带参数构造
工具会自动处理带参数的构造,不需要做特殊处理。唯一需要注意的是,你需要使用 argumentImports
为class/enum的参数提供 import 地址。
FFRoute
Parameter | Description | Default | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
name | 路由的名字 (e.g., "/settings") | required | showStatusBar | 是否显示状态栏 | true | routeName | 用于埋点收集数据的页面名字 | '' | pageRouteType | 路由的类型 (material, cupertino, transparent) | - | description | 路由的描述 | '' | exts | 其他扩展参数. | - | argumentImports | 某些参数的导入.有一些参数是类或者枚举,需要指定它们的导入地址 | - |
生成文件
环境
添加 dart 的 bin 的路径到你的系统 $PATH
.
cache\dart-sdk\bin
更多信息
不清楚的可以看掘金
激活
pub global activate ff_annotation_route
执行命令
到你的项目根目录下面执行.
ff_route <command> [arguments]
命令参数
可用的命令:
注解 Navigator 1.0
完整代码在 example 中
Main.dart
Push
Push name
Push name with arguments
- 参数必须是一个
Map<String, dynamic>
- 开启 --supper-arguments
注解 Navigator 2.0
完整代码在 完整代码在 example1 中
Main.dart
FFRouteInformationParser
主要用在 Web 平台,当你在浏览器上面输入的时候路由配置转换成为 RouteSettings
,或者当反馈给浏览器的时候将 RouteSettings
转换成路由配置
举个例子:
xxx?a=1&b=2
<=> RouteSettings(name:'xxx',arguments:<String, dynamic>{'a':'1','b':'2'})
FFRouterDelegate
用于创建和配置导航的委托,它提供 Navigator
中相似的方法.
你可以在 test_page_c.dart 页面里面找到更多的例子
Push
Push name
Push name with arguments
- 参数必须是一个
Map<String, dynamic>
- 开启 --supper-arguments
Code Hints
你能这样使用路由 'Routes.flutterCandiesTestPageE', 并且在编辑器中看到代码提示。 包括页面描述,构造,参数类型,参数名字,参数是否必填。
- 默认
- 开启 --supper-arguments
结语
路由我定
,人生中也有很多的十字路,以及选择需要我们去定。选择大于努力,有时候以为只有一条路,其实也可以有另外的选择。有时候以为还有其他的选择,其实只剩下一条路。站在人生十字路口希望我们都不再彷徨,不管是现在的我,还是将来的你,都不希望会后知后觉。- 做
Flutter
也两年了,从开始孤自一人研究到现在有很多小伙伴可以讨论,Flutter
让我学习到了很多东西,也认识了更多的人。不知道程序员能写多久的代码
,但从重庆轻轨做通信项目到转行来写代码,是真的因为喜欢。我想一个人能做自己喜欢的事情,应该也是一种幸福吧。 - 很感谢你能读到这里,希望本文对你有所帮助。爱Flutter,爱糖果,欢迎加入Flutter Candies,一起生产可爱的Flutter小糖果
QQ群:181398081。最最后放上Flutter Candies全家桶,真香。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!