路由管理

Catalogue   

一些问题

  • 路由存在的意义?
  • 如果是你,会如何设计?
  • dialog、bottomsheet、snackbar、dropmenubutton的底层实现是怎样的?
  • pop的时候路由是如何传递数据回到上一个界面的?
  • hero动画如何实现?

基本定义

相关类

  • Navigator:负责整个路由栈,在结构上实际是一个Overlay;
  • NavigatorState:管理整个栈,通过_RouteEntry列表管理栈。有push、pop等操作;
  • Overlay:完成页面的协调、展示;维护一个OveralyEntry列表;
  • _Theatre:_Theatre用来管理所有页面,在_Theatre中,可见/不可见的子结点都会转换成Element,但是在绘制的时候,_Theatre对应的_RenderTheatre只会把可见的子结点绘制出来;Overlay中创建,onstage表示舞台,offstage表示观众席;
  • OverlayEntry:用来在Overlay上展示Widget;
  • RouteSettings:用来保存路由的名字和参数;
  • Route:定义路由接口,保存RouteSettings;它的子类:
    • OverlayRoute:页面元素的创建,展示Widget,createOverlayEntries创建两个OverlayEntry,用于构建视图;
      • TransitionRoute:处理转场;
        • ModalRoute:可以阻止与不同路由的交互;
          • PopupRoute:
            • RawDialogRoute:
              • CupertinoDialogRoute:iOS风格的路由;
          • PageRoute:定义了路由构建及切换时转场动画的相关接口和属性;
            • MaterialPageRoute:Material风格的路由,通过builder设置路由具体页面;
              • CupertinoPageRoute:
              • PageRouteBuilder:自定义转场动画;
  • _RouteEntry:包含Route,状态机管理,各个生命周期的回调处理,标记转场是否有动画效果,通过transitionDelegate属性配置;
  • RouteTransitionRecord:_RouteEntry的父类;
  • NavigatorObserver:监控push、pop、replace、remove路由的情况
  • Page:继承于RouteSettings,自己创建Route,有一个key用来作为唯一性判断,用于设置Navigator的历史堆栈;
    • MaterialPage:
    • CupertinoPage:
  • Router:精细化管理页面,路由信息提供器、解析器;
  • RouteDelegate:定义路由行为,监听RouteInformationParser和应用状态,并构建Pages
  • RouteInformation:
  • RouteInformationParser:解析RouteInformation,它从RouteInformationProvider中获取RouteInformation,并将其解析为用户定义的数据类型;
  • RouteInformationProvider:负责通知RouteInformation变化;
  • RouterDelegate:定义了Router如何学习应用状态变化以及如何响应这些变化的应用特定行为。它的工作是监听RouteInformationParser和应用状态,并利用当前的Pages列表构建Navigator;
  • BackButtonDispatcher:向Router报告返回按钮按下的情况;

路由状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

enum _RouteLifecycle {
staging, // we will wait for transition delegate to decide what to do with this route.
//
// routes that are present:
//
add, // we'll want to run install, didAdd, etc; a route created by onGenerateInitialRoutes or by the initial widget.pages
adding, // we'll waiting for the future from didPush of top-most route to complete
// routes that are ready for transition.
push, // we'll want to run install, didPush, etc; a route added via push() and friends
pushReplace, // we'll want to run install, didPush, etc; a route added via pushReplace() and friends
pushing, // we're waiting for the future from didPush to complete
replace, // we'll want to run install, didReplace, etc; a route added via replace() and friends
idle, // route is being harmless
//
// routes that are not present:
//
// routes that should be included in route announcement and should still listen to transition changes.
pop, // we'll want to call didPop
complete, // we'll want to call didComplete,
remove, // we'll want to run didReplace/didRemove etc
// routes should not be included in route announcement but should still listen to transition changes.
popping, // we're waiting for the route to call finalizeRoute to switch to dispose
removing, // we are waiting for subsequent routes to be done animating, then will switch to dispose
// routes that are completely removed from the navigator and overlay.
dispose, // we will dispose the route momentarily
disposed, // we have disposed the route
}


状态变化

add–>adding–>idle

NavigatorState初始化的时候,通过混入RestorationMixin,didChangeDependencies的时候调用restoreState校验是否有initialRoute,有则进行初始路由初始化,初始化状态为add;该方法最后调用_flushHistoryUpdates,接着调用entry.handleAdd,状态修改为adding,接着continue下一次循环,执行entry.didAdd。

push–>pushing–>idle

Navigator.push,将Route封装成_RouteEntry加入到_history中,并调用_flushHistoryUpdates。该方法返回Future对象,可用于接收返回结果。在_flushHistoryUpdates中调用entry.handlePush,状态切换为pushing;push/pushReplace有一个转场动画,动画结束后状态变为idle。

pop–>poping–>dispose–>disposed

Navigator.pop,如果entry的settings是Page,先让外部来判断是否需要pop,否则直接entry.pop,设置状态为pop,执行_flushHistoryUpdates,接着执行entry.handlePop,切换状态到popping,执行route.didPop,完成返回值的传递、移除动画启动。didPop中调用navigator.finalizeRoute方法,状态切换到dispose,刷新_flushHistoryUpdates,状态到disposed。

remove–>removing–>dispose–>disposed

Navigator.removeRoute,调用entry.remove,将当前状态设置为remove,并调用_flushHistoryUpdates。调用entry.handleRemoval切换状态到revoming,接着切换状态到dispose,并添加到toBeDisposed中,然后切换状态到disposed,整个过程不涉及动画。

命名路由

通过名字进行路由跳转,Navigator.pushNamed(context, ‘/xxx/yyy’);通过_WidgetsAppState中的_onGenerateRoute方法来根据名字返回不同的Route。

嵌套路由

showGeneralDialog

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Future<T?> showGeneralDialog<T extends Object?>({
required BuildContext context,
required RoutePageBuilder pageBuilder,//自定义UI
bool barrierDismissible = false,//是否模态
String? barrierLabel,
Color barrierColor = const Color(0x80000000),
Duration transitionDuration = const Duration(milliseconds: 200),
RouteTransitionsBuilder? transitionBuilder,
bool useRootNavigator = true,//true表示从根节点弹窗
RouteSettings? routeSettings,
}) {
return Navigator.of(context, rootNavigator: useRootNavigator).push<T>(RawDialogRoute<T>(
pageBuilder: pageBuilder,
barrierDismissible: barrierDismissible,
barrierLabel: barrierLabel,
barrierColor: barrierColor,
transitionDuration: transitionDuration,
transitionBuilder: transitionBuilder,
settings: routeSettings,
));
}

fluro分析

  • 封装了原生代码,减少重复代码量;
  • 封装了默认的转场动画;
  • 所有路由组装成一个tree,统一管理所有路由,通过命名路由的方式跳转;

getx路由模块分析

  • 记录了Navigator的key,方便其他地方获取NavigatorState;
  • 封装Page,支持路由拦截,内置转场动画;

参考