美文网首页
认识一下Flutter中Navigator数据传递原理

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

作者: AndroidHint | 来源:发表于2020-03-15 11:49 被阅读0次

    引言

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

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

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

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

    2、我们还可以使用RouteRouteSetting参数进行数据的传递。例如

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

    在页面SearchPage可以这样获取传递过来的参数:

    String arg = ModalRoute.of(context).settings.arguments;
    

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

    源码解析

    大推论

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

    image
    context是一个StatefulElement,依次点开它的_parent变量,如下两图所示。 image
    image
    最终看到了Navigator的身影。也就是说,使用Navigator.push方法跳转到SearchPage,则相当于将SearchPage挂载在了Navigator这个Widget下,SearchPage相当于Navigator的一个子组件。

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

    image
    进入到ModalRouteof方法。
    static ModalRoute<T> of<T extends Object>(BuildContext context) {
        final _ModalScopeStatus widget = context.inheritFromWidgetOfExactType(_ModalScopeStatus);
        return widget?.route;
    }
    

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

    @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进行操作。

    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树中,如下图所示。

    image

    于是我们有理由得出如下大推论:

    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);
    }
    
    image
    结合打断点后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)

    相关文章

      网友评论

          本文标题:认识一下Flutter中Navigator数据传递原理

          本文链接:https://www.haomeiwen.com/subject/hhscehtx.html