不管app仍是webapp,路由都是必不可少的,相对于webapp,app的路由通常都更增强大和可控,这方面web实在太欠缺,而Flutter很明显彻底克服了web的缺点,拥有一个更为完善的路由模块,这也是Flutter整个框架的特色,吸取web开发优势,但也克服web那些显而易见的缺点,提供一个更为轻松高效的开发环境,好吧,接下来一块儿深刻了解这个模块吧。前端
能够理解Flutter仅仅提供一个View层,其实至关多的功能都要依赖原生,例如电池信息,位置信息,网络信息等等,更为简单的说Flutter就是Super WebView。
一样当咱们按下返回键时,就须要原生层告诉Flutter,弹出一个路由,让它返回上一级页面。
来到WidgetsBinding.initInstances方法:web
void initInstances() { ... SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation); ... }
接着_handleNavigationInvocation方法:网络
Future<dynamic> _handleNavigationInvocation(MethodCall methodCall) { switch (methodCall.method) { case 'popRoute': return handlePopRoute(); case 'pushRoute': return handlePushRoute(methodCall.arguments); } return new Future<Null>.value(); }
根据原生层的调用选择弹出一个路由或者压入一个路由,如今主要追踪弹出路由的处理。
接着handlePopRoute方法:app
Future<Null> handlePopRoute() async { for (WidgetsBindingObserver observer in new List<WidgetsBindingObserver>.from(_observers)) { if (await observer.didPopRoute()) return; } SystemNavigator.pop(); }
这里也是一个观察者模式的实现了,通知全部监听者弹出路由,这里主要一个处理就是若是didPopRoute返回false(也就是没有路由愿意处理)就交给系统默认处理,通常要么退出这个Activity,要么退出应用。
那么在哪里注册这个监听器并与Navigator组件关联起来的尼?
答案就在_WidgetsAppState这类里面:框架
void initState() { ... WidgetsBinding.instance.addObserver(this); }
再看一下这个类的didPopRoute和didPushRoute方法:webapp
// On Android: the user has pressed the back button. @override Future<bool> didPopRoute() async { final NavigatorState navigator = _navigator.currentState; return await navigator.maybePop(); } @override Future<bool> didPushRoute(String route) async { final NavigatorState navigator = _navigator.currentState; navigator.pushNamed(route); return true; }
在这里就把Navigator关联起来了。async
Navigator的职责是负责管理Route的,管理方式就是利用一个栈不停压入弹出,固然也能够直接替换其中某一个Route。而Route做为一个管理单元,主要负责建立对应的界面,响应Navigator压入路由和弹出路由。
Flutter定义路由的方式跟前端MVC框架是很类似的,你会看到有这种相似的:/home,/posts,/posts/:id等等,搞前端的同窗应该想到熟悉。ide
接着继续追踪路由弹出处理流程,看一下NavigatorState.maybePop方法:布局
Future<bool> maybePop([dynamic result]) async { final Route<dynamic> route = _history.last; assert(route._navigator == this); final RoutePopDisposition disposition = await route.willPop(); if (disposition != RoutePopDisposition.bubble && mounted) { if (disposition == RoutePopDisposition.pop) pop(result); return true; } return false; }
这个时候,Navigator会询问Route是否要本身处理仍是交给系统处理,当Route.willPop返回值为RoutePopDisposition.bubble时即交给系统处理,这里也简单介绍RoutePopDisposition三个枚举值:post
pop 弹出路由,正常状况返回上一级
doNotPop 不弹出,沉默处理,不少时候出如今一些表单填写的状况,必须完成页面内容,或者提示用户点击第二次才能退出
bubble 交给系统处理,通常直接退出应用
再看一下Route.willPop默认实现:
Future<RoutePopDisposition> willPop() async { return isFirst ? RoutePopDisposition.bubble : RoutePopDisposition.pop; }
先会判断自身是不是最后一个路由,若是是交给系统处理退出应用,若是不是弹出一个路由,很正常的行为实现。
因此当返回的是pop时,调用Navigator.pop方法:
bool pop([dynamic result]) { final Route<dynamic> route = _history.last; bool debugPredictedWouldPop; if (route.didPop(result ?? route.currentResult)) { if (_history.length > 1) { setState(() { _history.removeLast(); if (route._navigator != null) _poppedRoutes.add(route); _history.last.didPopNext(route); for (NavigatorObserver observer in widget.observers) observer.didPop(route, _history.last); }); } else { return false; } } else { assert(!debugPredictedWouldPop); } _cancelActivePointers(); return true; }
在弹出路由前,会调用Route.didPop方法,也能够看到就算以前Route.willPop返回值为pop,仍然能够在Route.didPop返回false改变这个行为,从而不弹出路由。
可是若是Route.didPop方法返回的是true,就会把当前路由弹出,并调起如今当前的路由didPopNext方法通知它已经回到前台,作好一些状态恢复工做,例如:拉取最新列表信息。
而最后的_cancelActivePointers方法,马上分发一个PointerCancel事件,这个时候手势识别器的状态会被重置,例如:双击手势,刚点了第一下就按了返回键,就会重置状态。
接着Route.didPop方法,当咱们退出一个页面的时候通常都会执行一个过渡动画,可是过渡动画的持续时间多少,Navigator没法知道,因此Route要本身负责调起NavigatorState.finalizeRoute方法,通知Navigator释放路由,而后Navigator会回调Route.dispose方法释放Route自身资源,路由的生命周期结束。
直接来到NavigatorState.pushNamed方法:
Future<dynamic> pushNamed(String name) { return push(_routeNamed(name)); }
再跳到_routeNamed方法:
Route<dynamic> _routeNamed(String name, { bool allowNull: false }) { final RouteSettings settings = new RouteSettings( name: name, isInitialRoute: _history.isEmpty, ); Route<dynamic> route = widget.onGenerateRoute(settings); if (route == null && !allowNull) { route = widget.onUnknownRoute(settings); } return route; }
也很简单根据名称查找路由,能够看看MaterialApp的实现:
Route<dynamic> _onGenerateRoute(RouteSettings settings) { final String name = settings.name; WidgetBuilder builder; if (name == Navigator.defaultRouteName && widget.home != null) builder = (BuildContext context) => widget.home; else builder = widget.routes[name]; if (builder != null) { return new MaterialPageRoute<dynamic>( builder: builder, settings: settings, ); } if (widget.onGenerateRoute != null) return widget.onGenerateRoute(settings); return null; }
咱们建立MaterialApp时都会传入一个route的map,而后就是根据map来建立route,就这么简单。
若是找到路由,就像咱们访问网页404,应该给一个友好的页面告诉用户不存在,就能够在onUnknownRoute回调中返回一个页面。
接着NavigatorState.push方法:
Future<dynamic> push(Route<dynamic> route) { setState(() { final Route<dynamic> oldRoute = _history.isNotEmpty ? _history.last : null; route._navigator = this; route.install(_currentOverlayEntry); _history.add(route); route.didPush(); route.didChangeNext(null); if (oldRoute != null) oldRoute.didChangeNext(route); for (NavigatorObserver observer in widget.observers) observer.didPush(route, oldRoute); }); _cancelActivePointers(); return route.popped; }
重点看Route.install方法,首先了解一下Route的继承关系:
能够看到Route的实现都继承自OverlayRoute,而OverlayRoute.install的实现:
void install(OverlayEntry insertionPoint) { assert(_overlayEntries.isEmpty); _overlayEntries.addAll(createOverlayEntries()); navigator.overlay?.insertAll(_overlayEntries, above: insertionPoint); super.install(insertionPoint); }
navigator会把Route.createOverlayEntries建立的OverlayEntries添加到本身的Overlay组件上;而createOverlayEntries方法干了啥尼,再来到ModalRoute.createOverlayEntries:
Iterable<OverlayEntry> createOverlayEntries() sync* { yield new OverlayEntry(builder: _buildModalBarrier); yield new OverlayEntry(builder: _buildModalScope, maintainState: maintainState); }
能够看到构建两个OverlayEntry,咱们常常看到对话框后面还有一层遮罩,就是由这里产生的。
再看一下OverlayEntry有两个重要的属性opaque和maintainState,当咱们把OverlayEntry添加到Navigator的Overlay组件时,Overlay组件构建过程处理是这样的:
Widget build(BuildContext context) { // These lists are filled backwards. For the offstage children that // does not matter since they aren't rendered, but for the onstage // children we reverse the list below before adding it to the tree. final List<Widget> onstageChildren = <Widget>[]; final List<Widget> offstageChildren = <Widget>[]; bool onstage = true; for (int i = _entries.length - 1; i >= 0; i -= 1) { final OverlayEntry entry = _entries[i]; if (onstage) { onstageChildren.add(new _OverlayEntry(entry)); if (entry.opaque) onstage = false; } else if (entry.maintainState) { offstageChildren.add(new TickerMode(enabled: false, child: new _OverlayEntry(entry))); } } return new _Theatre( onstage: new Stack( fit: StackFit.expand, children: onstageChildren.reversed.toList(growable: false), ), offstage: offstageChildren, ); }
当有一个OverlayEntry的opaque为true时(就是不透明看不到下面的页面),默认状况下在它之下OverlayEntry不会实例化(也不必),可是若是设置maintainState为true时,OverlayEntry会build出组件树,可是这些组件不会被布局和绘制,主要用于维持组件状态。
当Route.dipose方法调起后,Route的OverlayEntry才会从Navigator的Overlay组件移除。
再回头当Route.install方法调起后,通常过渡动画会在这里构建,接会调用Route.didPush方法,过渡动画会在这里播放,最后再调用前一个路由的didChangeNext方法通知它被退到后台,能够在这个方法里保存本身状态信息,等下次回到前台时恢复。
在咱们初始化的时候,咱们默认初始化路由是:‘/’,可是咱们若是初始化路由是这样的:‘/posts/123’,框架是怎样处理的尼?
咱们来到NavigatorState.initState方法
void initState() { super.initState(); for (NavigatorObserver observer in widget.observers) { assert(observer.navigator == null); observer._navigator = this; } String initialRouteName = widget.initialRoute ?? Navigator.defaultRouteName; if (initialRouteName.startsWith('/') && initialRouteName.length > 1) { initialRouteName = initialRouteName.substring(1); // strip leading '/' assert(Navigator.defaultRouteName == '/'); final List<String> plannedInitialRouteNames = <String>[ Navigator.defaultRouteName, ]; final List<Route<dynamic>> plannedInitialRoutes = <Route<dynamic>>[ _routeNamed(Navigator.defaultRouteName, allowNull: true), ]; final List<String> routeParts = initialRouteName.split('/'); if (initialRouteName.isNotEmpty) { String routeName = ''; for (String part in routeParts) { routeName += '/$part'; plannedInitialRouteNames.add(routeName); plannedInitialRoutes.add(_routeNamed(routeName, allowNull: true)); } } if (plannedInitialRoutes.contains(null)) { push(_routeNamed(Navigator.defaultRouteName)); } else { for (Route<dynamic> route in plannedInitialRoutes) push(route); //连续压入 } } else { Route<dynamic> route; if (initialRouteName != Navigator.defaultRouteName) route = _routeNamed(initialRouteName, allowNull: true); if (route == null) route = _routeNamed(Navigator.defaultRouteName); push(route); } for (Route<dynamic> route in _history) _initialOverlayEntries.addAll(route.overlayEntries); }
整个处理也很简单,直接把/posts和/posts/123压入路由栈中,而不是仅仅只是把/posts/123压进去,这跟web的url跳转就有点出入了,值得注意。