在开始以前,咱们先介绍一个貌似绝不相关的概念,至于缘由,一是由于后面的某个概念和它具备相关性,二是由于这个概念太简单,不足以以一整篇篇幅来介绍它,因此不如就在这里顺带着介绍一下。api
一句话总结 InheritedWidget
就是「在视图树上更有效的向下传递信息的 widget」。浏览器
abstract class InheritedWidget extends ProxyWidget {
const InheritedWidget({ Key key, Widget child })
: super(key: key, child: child);
@override
InheritedElement createElement() => InheritedElement(this);
@protected
bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
复制代码
updateShouldNotify()
方法用来控制对其实现的子类是否在 rebuild 过程当中一样进行 rebuild,例如,当此 widget 的数据并未改变时,可能并不须要对其进行更新。markdown
因此,相比于通常的 widget,它主要多了个在视图树上实现「信息传递」的功能,那它的信息传递的功能又是如何实现的呢——借助 BuildContext
类,咱们线看一个例子。app
class FrogColor extends InheritedWidget {
const FrogColor({
Key key,
@required this.color,
@required Widget child,
}) : assert(color != null),
assert(child != null),
super(key: key, child: child);
final Color color;
static FrogColor of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<FrogColor>();
}
@override
bool updateShouldNotify(FrogColor old) => color != old.color;
}
复制代码
of()
方法接收 BuildContext
参数,并返回参数的 dependOnInheritedWidgetOfExactType()
方法调用结果,而该方法的实如今 BuildContext
类的子类 Element
类中。ide
这个从视图树上按名称摘果子的过程并不难理解。好了,关于 InheritedWidget
的部分咱们就了解这么多,下面回归本篇的核心主旨——路由部分。函数
Navigator
以栈的方式管理着它的家族控件们。正如在 Android 中经过一个栈来管理 Activity
,每一个 Activity
做为一个单独的页面的原则, Flutter 中也以栈的方式管理着咱们须要的页面,不过每一个页面再也不是 Activity
,而变成了 route。oop
说到 Navigator
,咱们能够在脑海中造成这样一种画像,在桌子上摞着一叠图纸,咱们能看到最上面的那一张画了些什么,可是没法其余在下面的图纸的内容。若是如今把最上面的那张图纸拿开,原先自上而下的第二张此刻就变成了最上面的那张图纸,此时咱们看到画像就仍是新的最上面的那幅。那再放置一张新的画像在这一摞图画之上,可见的图画就又被更新了。post
当咱们须要添加新的「图画」时,只须要使用 Navigator
的 push
系列方法就能够了,push
系列方法有好些个,忽略其余的附加操做,它们能够分为两类—— push
和 pushNamed
。学习
push
Navigator.of(context).push(MaterialPageRoute(builder: (context) => SignUpPage()));
复制代码
这句代码并不特别,是跳转一个新界面的通常作法。MaterialPageRoute
是 Route
的子类,builder
参数返回新界面的 Widget
实例。ui
Navigator
是 StatefulWidget
的子类,对应的 State
为 NavigatorState
。Navigator.of(context)
方法返回 NavigatorState
实例,和上面 InheritedWidget
相似,借助 BuildContext
的 findRootAncestorStateOfType()
方法在 Element 树上寻找对应的 StatefulElement
,返回能够和泛型指定的 State
类型匹配的 State
对象。
在深刻 push()
方法以前,咱们先借助 devtools 了解一下 Flutter 页面的层级结构,可以帮助咱们更好地理解下面的流程。
接下来就是调用 Navigator
的 push()
方法了,这部分的逻辑比较复杂,我尝试着按个人理解绘制了一张流程图,对照着来理解整个过程。
graph TB A["push()"] --> B["_history.add()"] A --> C["_flushHistoryUpdates"] A --> D["_afterNavigation()"] C --> E["entry.handlePush()"] C --> F["overlay.rearrange(_allRouteOverlayEntries)"] E --> G["entry.handlePush()"] G --> H["_overlayEntries.addAll(createOverlayEntries())"] H --> I["_buildModalBarrier()"] H --> J["_buildModalScope()"] J --> K["_ModalScope<_ModalScopeState>"] K --> L["build()"] L --> M["ModalRoute.buildPage()"] M --> N["MaterialPageRoute.builder"] D --> O["_cacelActivePointers()"] O --> P["setState()"] P --> Q["build()"] Q --> R["Overlay.initialEntries = _allRouteOverlayEntries"] S{{"for (entry in _history) yield entry.route.overlayEntries"}} F -->|"_allRouteOverlayEntries"| S H -->|"_overlayEntries"| S F -.-|"_allRouteOverlayEntries"| R
push()
方法接受 Route
型的参数,并在方法内将其封装为 _RouteEntry
型。Navigator
类有一个成员 _history
,是一个 OverlayEntry
对象的集合,push()
方法将封装好的 _RouteEntry
对象添加到 _history
列表中。以后 push()
方法调用 _RouteEntry
的 handlePush()
方法,建立 「_ModalBarrir」 和 「_ModalScope」,它们都是 Widget 对象,前者是用来隔毫不同界面之间的交互操做(例如手势操做),后者是对咱们目标跳转页面的封装。最后 push()
方法调用 _afterNavigation()
方法刷新 Navigator
,导致 build()
方法被调用,在此方法中,Navigator
经过 GlobalKey
获取到全局的 Overlay
对象,并将被 _OverlayEntryWidget
对象包裹的 「_ModalScope」页面更新到 Overlay
中,这样咱们的界面就能够显示在页面层级中了。
pushNamed
MaterialApp
中支持经过 onGenerateRoute
参数来构建路由表。它是一个方法,形式为 Route<dynamic> Function(RouteSettings settings)
,根据传入的 RouteSettings
对象参数,返回对应的 Route
实例。RouteSettings
类拥有两个成员变量分别为 final String name
和 final Object arguments
。而 Navigator
和 NavigatorState
的 pushNamed()
方法参数接收的正是这两个对象。
Future<T> pushNamed<T extends Object>(
String routeName, {
Object arguments,
}) {
return push<T>(_routeNamed<T>(routeName, arguments: arguments));
}
复制代码
能够看到 pushNamed()
方法最终调用仍是上面介绍的 push()
方法,可是参数则经过 _routeName()
方法来构建。
Route<T> _routeNamed<T>(String name, { @required Object arguments, bool allowNull = false }) {
// ...
final RouteSettings settings = RouteSettings(
name: name,
arguments: arguments,
);
Route<T> route = widget.onGenerateRoute(settings) as Route<T>;
if (route == null && !allowNull) {
route = widget.onUnknownRoute(settings) as Route<T>;
}
return route;
}
复制代码
使用 flutter 命令行运行 flutter run --route=/signup
查看 demo。
pop
当调用 pop()
方法时,会将页面栈早上层的页面视图弹出,显示出下面一张的视图。
在这个过程当中,_flushHistoryUpdates()
方法依然发挥着重要的做用,经过 _RouteEntry.currentState
变量控制弹出的过程,分别为pop
、poping
、remove
、removing
、dispose
、disposed
,并在这些过程当中移除 NavigatorState._history
中的对应的 _RouteEntry
实例,在刷新视图时,Overlay
获得更新,被移除的实例会将包裹的页面移除 Overlay 层。
那么在这个过程当中,前一个页面的数据是如何传递到后一个页面的呢?
graph LR A["NavigatorState.pop(result)"] --> B["_RouteEntry.pop(result)"] --> C["Route.didPop(result)"] --> D["Route.didComplete(result)"]
在通过上面的调用后,pop()
方法的参数 result
被传递到 Route.didComplete()
方法。
void didComplete(T result) {
_popCompleter.complete(result ?? currentResult);
}
复制代码
_popCompleter
对象是 Completer
类的实例,而 _popCompleter
的 future
属性在 NavigatorState.push()
方法调用时被返回。
/// Route
Future<T> get popped => _popCompleter.future;
/// NavigatorState
Future<T> push<T extends Object>(Route<T> route) {
// ...
return route.popped;
}
复制代码
因此后一个页面调用 pop()
方法返回的结果能被前一个页面在调用 push()
方法后以 Future
的形式接收到,诸以下面的形式:
Navigator.of(context).push(MaterialPageRoute(builder: (context) => SignUpPage()))
.then((value) => print("the result from next page: $value"));
复制代码
Navigator
到目前为止,一切都运行良好,可是它的局限也很明显。首先,它没法一次性压入多个页面;其次,它只能弹出最上层的页面,对于某些场景下弹出下层页面的需求则没法知足。因此 Navigator 2.0 就应运而生了。
在 Flutter 迭代到 1.22 版本后,关于 Navigator 的部分添加了一些新的 api:
Page
—— 抽象的「页面」的概念,对 Route
配置选项的一种描述;Router
—— 管家角色,应用中页面打开或关闭的调度员,监听来自于系统的路由信息(如启动路由、新路由加入或者系统返回按钮的消息等);RouteInformationProvider
—— 更改路由获取到的页面的名字;RouteInfomationParser
—— 接收来自 RouteInfomationProvider
的 RouteInfomation
并将其转化为泛型约束的数据类型;RouterDelegate
—— 输入来自 RouteInformationParser
的数据,负责将提供的 navigator 页面插入视图树,同时接受监听更新视图;BackButtonDispatcher
—— 监听返回按钮事件。Navigator 2.0 的概念和以前介绍过的 Flutter 视图树比较类似——Widget
保存着视图的配置,经过 Widget
对象建立对应的 Element
和 RenderObject
——Page
对象是关于路由的配置的抽象的概念,而经过它的 createRoute()
方法建立 Route
对象。
Page
Page
是一个页面的抽象,继承自 RouteSettings
类,经过 name
属性来标识页面。正如 Widget
到 Element
经过 createElement()
方法,Page
中也有一个方法 createRoute()
用来建立 Route
实例。
经过上面 Navigator 1.0 的分析,咱们知道 Route
是 Flutter 路由进行页面切换的载体,包裹着真正的页面在栈中「腾挪闪转」,从而实现页面切换的功能。
RouterInformationProvider
这个类经过它的 value
属性传递值给 RouteInformationParser
类的 parseRouteInformation
方法,该值即 RouteInformation
对象,储存路由的地址,经过该地址能够控制页面跳转。
例如当咱们在浏览器的地址栏输入 「/index」后缀做为新的跳转地址后,RouteInformationParser
类的 parseRouteInformation
方法便可接收到 location
属性存储有 「/index」值的 RouteInformation
对象。
RouteInformationParser
该类提供了两个方法,分别是 parseRouteInformation
和 restoreRouteInformation
。
parseRouteInformation
方法接收地址信息—— RouteInformation
,而后返回 Future<T>
类型对象,「T」是一个约定的任意类型,返回的 Future<T>
类型将在 RouterDelegate
类的 setNewRoutePath
方法被接收,能够在该方法中真正实现页面添加跳转的逻辑。
一般该方法的调用来自浏览器地址栏输入地址后跳转,而咱们经过 navigator 实现的界面跳转不会致使该方法被调用。
restoreRouteInformation
方法用来恢复浏览历史页面,好比咱们须要作「前进」或「后退」的功能而保持浏览器地址栏中的地址不变,则能够经过 Router
类的 navigate()
方法强制上报路由信息从而触发该方法。该方法返回的 RouteInformation
对象被 parseRouteInformation
方法接收和处理。
RouterDelegate
该类是处理路由地址的主要类,页面的压入与弹出都在这个类中进行。
首先,这个类经过 setNewRoutePath
方法接收新的路由地址,而后对新的地址进行查找(通常在用户本身维护的路由表中),将对应的页面压入栈。其次,该类提供了 build
方法,Router
对象会调用该方法获取视图树对象,因此该方法中应当返回能表明当前视图树的 Widget
对象,以供系统对显示视图进行更新。
Router
管理页面的管家。它不只负责页面的构建,还负责业务逻辑的处理与分发。
上面介绍到 Navigator 2.0 的思想在于把一部分的页面栈的操做权限下放给用户,在 App 中,若是咱们须要对页面栈进行排序、插入、多页面插入、删除、多页面删除,或者对浏览器更新与加载方式等进行操做时,须要用到上面介绍的一些对象,这些对象都在 Router
中持有引用,因此咱们就可使用 Router
对象获取到这些对象的引用,而 Router
对象能够经过其静态方法 of()
获取。
大体的介绍就这么多,用法能够看这个 demo 注。下面简单串一下系统的运行流程。
首先 MaterialApp.router()
构造方法会传入 routeInformationParser
和 routerDelegate
等对象,_MateiralAppState
对象在 build()
方法中调用 _buildWidgetApp()
方法构造 WidgetsApp
对象,由于 routerDelegate
对象是必填字段,因此 bool get _usesRouter => widget.routerDelegate != null;
字段为 true
,会经过 WidgetsApp.router
构造函数构造,而后在 _WidgetsAppState
类的 build()
方法中构造 Router
对象,因此它的层级结构以下(固然,它们之间还穿插着其余的包装类):
Router
类继承自 StatefulWidget
,那么老规矩,仍是看 _RouterState
的 build()
方法:
Widget build(BuildContext context) {
return _RouterScope(
routeInformationProvider: widget.routeInformationProvider,
backButtonDispatcher: widget.backButtonDispatcher,
routeInformationParser: widget.routeInformationParser,
routerDelegate: widget.routerDelegate,
routerState: this,
child: Builder(
// We use a Builder so that the build method below
// will have a BuildContext that contains the _RouterScope.
builder: widget.routerDelegate.build,
),
);
}
复制代码
可见,最终仍是会调用 RouterDelegate
的 build()
方法来建立页面,该方法由开发者实现。
咱们对该方法的实现以下:
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
pages: List.of(_pages),
onPopPage: (route, result) {
if (_pages.length > 1 && route.settings is MyPage) {
final MyPage<dynamic>? removed = _pages.lastWhere(
(element) => element.name == route.settings.name,
);
if (removed != null) {
_pages.remove(removed);
notifyListeners();
}
}
return route.didPop(result);
},
);
}
复制代码
Navigator
都很熟悉了,可是这里的用法又和上面介绍的两种用法都不同。
这里经过 Navigator
的 pages
属性,将页面列表 List<Page>
传递进去,当视图配置有变动时,触发视图更新,此方法被调用,而后经过比较 pages
是否已产生变化,来决定是否更新页面,最终会调用 Navigator
的 _updatePages
方法。这个方法的内容有点多,咱们就不作具体说明了,只大概说一下它的工做流程。
这个方法比较新的 pages
列表和旧的 _history
列表(元素为 _RouteEntry
类型),而后产生新的 _history
列表。这个方法大体和 RenderObjectElement.updateChildren()
方法流程相同。
须要注意的是,这个方法全程在围绕着两个列表进行——旧的路由列表 _history
以及新的页面列表 widget.pages
,咱们把前者称为「oldEntries」,把后者称为 「newPages」,经过两个列表共同比对,剔除 oldEntries 中非 Page
型的节点,而用 newPages 中的节点更新对应的 oldEntries 的节点。
首先从 List 头开始同步节点,并记录非 Page
的路由,直到匹配完全部的节点。
从 List 尾部开始遍历,但不一样步节点,直到再也不有匹配的节点,而后最后同步全部的节点,之因此这么作,是由于咱们想以从头至尾的顺序来同步这些节点。此时,咱们将旧 List 和新 List 缩小到节点再也不匹配的位置。
遍历旧列表被收缩的部分,得到一个存储 Key
值的 List。
正向遍历新 List 被收缩的部分(即去除已遍历两端的中间部分):
Key
元素建立 _RouteEntry
对象并将其记录为 transitionDelegate
(转场页面);Key
的元素列表(若是存在的话)。再次遍历旧 List 被收缩的部分,并记录 _RouteEntry
和非 Page
路由(须要从 transitionDelegate
中被移除)。
从列表尾部再次遍历,同步节点状态,并记录非 Page
页面。
根据 transitionDelegate
配置转场效果。
将非 Page
路由从新填充回新的 _history
。
更新过 _history
以后,剩下的流程就和 Navigator 1.0 中介绍的相同了——经过 Overlay
对象更新页面栈,完成页面显示和切换的需求。
Navigator 2.0 的思路就是将页面的排列和更替经过一个 Page
列表—— pages
彻底交给开发者,开发者只须要维护好 pages
,转化为真正可显示的界面的过程就交给 Flutter engine 便可。