认识一下Flutter中Navigator数据传递原理

引言

在Flutter中,路由间的页面跳转使用的是Navigator.pushNavigator.pop方法。html

在页面跳转时如何将数据传递过去,目前有两种方法:node

一、目标页面的构造函数显式接收参数。例如跳转过去的是SearchPage,接收一个字符串参数,则以下所示。bash

Navigator.push(context, new MaterialPageRoute(builder: (BuildContext context) => new SearchPage("传递的参数")));
复制代码

二、咱们还可使用RouteRouteSetting参数进行数据的传递。例如markdown

Navigator.push(context, new MaterialPageRoute(builder: (BuildContext context) => new SearchPage()
    , settings: RouteSettings(arguments: "传递的参数")));
复制代码

在页面SearchPage能够这样获取传递过来的参数:ide

String arg = ModalRoute.of(context).settings.arguments;
复制代码

这里咱们想说一下第二种方式数据传递的原理,让咱们从源码中一窥究竟吧!函数

源码解析

大推论

究竟怎么开始分析源码呢?咱们用打断点的形式,就能一步一步地看到每个方法的执行过程,既然最终的目标页是SearchPage,那么咱们就将断点打在SearchPagebuild方法,因为SearchPage是一个StatefulWidget,则断点打在了SearchPageStatebuild方法里,以下图所示。 oop

context是一个 StatefulElement,依次点开它的 _parent变量,以下两图所示。

最终看到了 Navigator的身影。也就是说,使用 Navigator.push方法跳转到 SearchPage,则至关于将 SearchPage挂载在了 Navigator这个 Widget下, SearchPage至关于 Navigator的一个子组件。

接下来,咱们将断点打在SearchPage中使用ModalRoute.of方法这一行,以下图所示。 ui

进入到 ModalRouteof方法。

static ModalRoute<T> of<T extends Object>(BuildContext context) {
    final _ModalScopeStatus widget = context.inheritFromWidgetOfExactType(_ModalScopeStatus);
    return widget?.route;
}
复制代码

这里的context就是SearchPageElement,调用了inheritFromWidgetOfExactType方法,并将_ModalScopeStatus做为参数传递过去。this

@override
  InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
    if (ancestor != null) {
      assert(ancestor is InheritedElement);
      return inheritFromElement(ancestor, aspect: aspect);
    }
    _hadUnsatisfiedDependencies = true;
    return null;
 }
复制代码

上面的方法逻辑也挺简单的,_inheritedWidgets是一个Map,若是不为空则获取_inheritedWidgetskey_ModalScopeStatusInheritedElement对象。那么_inheritedWidgets是何时被赋值的呢?咱们全局搜索一下,发如今 _updateInheritance中有对_inheritedWidgets进行操做。spa

void _updateInheritance() {
    assert(_active);
    final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
    if (incomingWidgets != null)
      _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
    else
      _inheritedWidgets = HashMap<Type, InheritedElement>();
    _inheritedWidgets[widget.runtimeType] = this;
 }
复制代码

首先它会将父类(_parent)的_inheritedWidgets赋值给本身的_inheritedWidgets,而后对于_inheritedWidgets的当前运行类型,将本身(this)进行赋值。

如今让咱们回过头来看一下上面的ModalRoute.of方法。

static ModalRoute<T> of<T extends Object>(BuildContext context) {
    final _ModalScopeStatus widget = context.inheritFromWidgetOfExactType(_ModalScopeStatus);
    return widget?.route;
}
复制代码

第一行返回了_ModalScopeStatus对象。根据上面的结论,咱们知道_inheritedWidgets[_ModalScopeStatus]就是在_ModalScopeStatus_updateInheritance进行赋值的。

也就是说context.inheritFromWidgetOfExactType实际上是从下往上寻找_ModalScopeStatus对象,而后of方法会返回_ModalScopeStatusroute变量。

NavigatorSearchPageWidget树能够看到,_ModalScopeStatus的确存在于上述的Widget树中,以下图所示。

因而咱们有理由得出以下大推论:

Navigator.push方法自上而下地产生了从Navigator到_ModalScopeStatus到SearchPage的Widget树,而且Navigator将路由信息存储在了_ModalScopeStatus这个Widget中,SearchPage自下而上寻找_ModalScopeStatus并获取其中的路由信息。

验证大推论

第一个小推论

为了验证上述推论的正确性,咱们从Navigatorbuild方法出发,一步步地看是否真的有将Route对象传递到_ModalScopeStatus对象。

Widget build(BuildContext context) {
    return Listener(
      onPointerDown: _handlePointerDown,
      onPointerUp: _handlePointerUpOrCancel,
      onPointerCancel: _handlePointerUpOrCancel,
      child: AbsorbPointer(
        absorbing: false, // it's mutated directly by _cancelActivePointers above child: FocusScope( node: focusScopeNode, autofocus: true, child: Overlay( key: _overlayKey, initialEntries: _initialOverlayEntries, ), ), ), ); } 复制代码

从上面的build方法与上面打断点时的Widget树得知,Navigator的子Widget树为Listener->AbsorbPointer->FocusScope->Overlay,而Overlaybuild方法以下。

Widget build(BuildContext context) {
    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(_OverlayEntry(entry));
        if (entry.opaque)
          onstage = false;
      } else if (entry.maintainState) {
        offstageChildren.add(TickerMode(enabled: false, child: _OverlayEntry(entry)));
      }
    }
    return _Theatre(
      onstage: Stack(
        fit: StackFit.expand,
        children: onstageChildren.reversed.toList(growable: false),
      ),
      offstage: offstageChildren,
    );
 }
复制代码

Overlay的子Widget树为_Theatre->Stack,而StackchildrenonstageChildren列表,则Stack的子Widget_OverlayEntry,而_OverlayEntrybuild方法以下。

Widget build(BuildContext context) {
    return widget.entry.builder(context);
}
复制代码

结合打断点后 Navigator的子 Widget树得知, _OverlayEntry的子 Widget_ModalScope,也就是说咱们得出第一个小推论:

widget.entry.builder(context)返回的Widget就是_ModalScope。

验证第一个小推论

咱们验证一下对不对,widget.entry实际上是从OverlayState_enties列表中获取的,而_enties列表是在Navigatorbuild方法中经过Overlay传递_initialOverlayEntries参数初始化的。

child: Overlay(
    key: _overlayKey,
    initialEntries: _initialOverlayEntries,
),
复制代码

_initialOverlayEntries是在NavigatorinitState初始化的。

void initState() {
    ...省略
    for (Route<dynamic> route in _history)
      _initialOverlayEntries.addAll(route.overlayEntries);
}
复制代码

这里传递进来的是Route对象的overlayEntries,而Route对象的overlayEntries,而这里的Route对象实际上是MaterialPageRoute,因此的overlayEntriesMaterialPageRoute的父类OverlayRoute被重写了,因此它真实的赋值是在OverlayRouteinstall方法里面,而install方法在Navigator.push方法中就被调用了,因此咱们看一下install方法。

void install(OverlayEntry insertionPoint) {
    assert(_overlayEntries.isEmpty);
    _overlayEntries.addAll(createOverlayEntries());
    navigator.overlay?.insertAll(_overlayEntries, above: insertionPoint);
    super.install(insertionPoint);
}
复制代码

_overlayEntries添加了createOverlayEntries方法的执行结果。

Iterable<OverlayEntry> createOverlayEntries() sync* {
    yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier);
    yield OverlayEntry(builder: _buildModalScope, maintainState: maintainState);
}
复制代码

上面方法构建了一个OverlayEntry对象,其中的builder参数传了_buildModalScope,而_buildModalScope返回的是_ModalScope对象。

Widget _buildModalScope(BuildContext context) {
    return _modalScopeCache ??= _ModalScope<T>(
      key: _scopeKey,
      route: this,
      // _ModalScope calls buildTransitions() and buildChild(), defined above
    );
}
复制代码

到这里,咱们终于验证了第一个小推论的正确性,即_OverlayEntry的子Widget就是_ModalScope,并且这个_ModalScope还包含了路由信息,就是其中的route变量。

由小推论验证大推论的正确性

咱们再来看_ModalScopebuild方法。

Widget build(BuildContext context) {
    return _ModalScopeStatus(
      route: widget.route,
      ...省略
    );
 }
复制代码

它返回了_ModalScopeStatus,并将它的route变量赋值给了_ModalScopeStatusroute变量,咱们上面说过_ModalScoperoute变量就是MaterialPageRoute对象。

也就是说只要拿到了_ModalScopeStatus对象,就能经过它的route方法获取MaterialPageRoute对象,并经过其中的setting变量获取RouteSetting对象,而RouteSetting对象的arguments变量就是路由传递进来的参数了。

看到这里,咱们就不难理解为何在SearchPage中使用下面这样的方法能获取路由传递进来的数据的缘由了。

String arg = ModalRoute.of(context).settings.arguments;
复制代码

由于经过ModalRoute.of(context)SearchPage自下而上地寻找_ModalScopeStatus对象,_ModalScopeStatus对象找到后经过route变量找到Route对象,也就是MaterialPageRoute对象,而后经过访问MaterialPageRoute对象的setting.arguments就能拿到Navigator传递进来的参数了,这就是Navigator数据传递的原理。

从上述分析中,咱们知道了Flutter中一个重要的功能型组件,它就是InheritedWidget,经过它咱们能够实现跨组件的数据共享。有关更多InheritedWidget的使用方法,能够参考 数据共享(InheritedWidget)

相关文章
相关标签/搜索