Flutter之Navigator源码解析

Flutter中,页面的跳转是经过Navigator来实现。经过几句简单的代码就能够实现页面的跳转并传递对应的参数。那么具体实现是怎样的尼?下面就来一窥究竟。数组

一、根Navigator

Flutter中,一切皆WidgetNavigator也不能例外。但咱们并无主动添加Navigator,可是又能够经过Navigator来进行页面跳转,这是怎么回事尼?缓存

咱们在进行Flutter开发时,必须以MaterialAppWidgetsApp来做为第一个Widget,不然就会出错。也就是在WidgetsApp中(MaterialApp是对WidgetsApp的包装),Flutter默认给咱们添加了第一个Navigator,通常跳转都是根据这个Navigator来进行的。这也就是咱们没有主动添加Navigator,但却可以经过Navigator来进行页面跳转的缘由。以下图所示。 markdown

从图中能够清晰的看到存在于 Widget树中的第一个 Navigator,即根 Navigator

对于Navigator,咱们通常都是经过Navigator.of(context)来获取NavigatorState对象,而后经过NavigatorState对象中的函数来实现页面跳转、关闭、替换等。下面来看Navigator.of(context)的实现。ide

static NavigatorState of(
    BuildContext context, {
    bool rootNavigator = false,
    bool nullOk = false,
  }) {
    final NavigatorState navigator = rootNavigator
        //获取根Navigator
        ? context.findRootAncestorStateOfType<NavigatorState>()
        //获取距离当前Widget最近的Navigator
        : context.findAncestorStateOfType<NavigatorState>();
    return navigator;
  }
复制代码

rootNavigatortrue表示获取根Navigator,不然获取距离当前Widget最近的Navigator,默认为false函数

能够发现,这里要想获取根Navigator,就须要传递一个context对象,但在某些需求(如token失效跳转登陆页)下,没法拿到context对象,但又须要跳转,那这该怎么办尼?post

这时候咱们能够给根Navigator传递一个key,而后根据这个key拿到NavigatorState对象。而MaterialApp又给咱们提供了向根Navigator传递key的机会。动画

class MaterialApp extends StatefulWidget {
  const MaterialApp({
    Key key,
    //自定义key
    this.navigatorKey,
    ...
  })
}
复制代码

经过给navigatorKey赋一个GlobalKey对象,而后经过这个对象就能够不须要context来得到根Navigator,从而进行页面的跳转、返回、替换等。ui

二、push页面源码解析

当咱们正确的拿到NavigatorState对象后,就能够经过push函数来跳转到到新的页面。this

@optionalTypeArgs
  Future<T> push<T extends Object>(Route<T> route) {
    //获取上一个route
    final Route<dynamic> oldRoute = _history.isNotEmpty ? _history.last : null;
    route._navigator = this;
    //获取当前OverlayEntry对象
    route.install(_currentOverlayEntry);
    //将当前route添加到集合中
    _history.add(route);
    route.didPush();
    route.didChangeNext(null);
    if (oldRoute != null) {
      oldRoute.didChangeNext(route);
      route.didChangePrevious(oldRoute);
    }
    for (NavigatorObserver observer in widget.observers)
      observer.didPush(route, oldRoute);
    RouteNotificationMessages.maybeNotifyRouteChange(_routePushedMethod, route, oldRoute);
    _afterNavigation(route);
    return route.popped;
  }
复制代码

在这里,route(路由)是一个很是重要的概念,它对页面进行了抽象。在Flutter中,一个页面对应一个route对象。_historyNavigatorState中的一个数组,也是一般所说的路由栈。在跳转一个新页面时,会将route对象添加到_history数组中,弹出页面则从该数组中移除route对象。spa

在上面代码中,route.install(_currentOverlayEntry)是页面跳转的核心实现。install函数在Route中是一个空实现,在其子类ModalRoute中进行了具体实现。

abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T> {

  ......
  
  @override
  void install(OverlayEntry insertionPoint) {
    super.install(insertionPoint);
    _animationProxy = ProxyAnimation(super.animation);
    _secondaryAnimationProxy = ProxyAnimation(super.secondaryAnimation);
  }
  ......
}
复制代码

ModalRoute里的install函数中主要作了动画相关的处理,而后交给父类执行。在其父类TransitionRoute主要是作动画的建立及相关处理,并交给其父类处理。那么再来看TransitionRoute的父类OverlayRoute

abstract class OverlayRoute<T> extends Route<T> {

  .....

  /// Subclasses should override this getter to return the builders for the overlay.
  Iterable<OverlayEntry> createOverlayEntries();

  /// The entries this route has placed in the overlay.
  @override
  List<OverlayEntry> get overlayEntries => _overlayEntries;
  final List<OverlayEntry> _overlayEntries = <OverlayEntry>[];

  @override
  void install(OverlayEntry insertionPoint) {
    //建立页面对应的两个OverlayEntry对象并加入到_overlayEntries中,以待后续处理。
    _overlayEntries.addAll(createOverlayEntries());
    //进行UI的更新
    navigator.overlay?.insertAll(_overlayEntries, above: insertionPoint);
    //空实现
    super.install(insertionPoint);
  }
  
  ......
  
}
复制代码

OverlayRoute中有一个抽象函数createOverlayEntries,该函数须要在子类实现。代码以下。

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

yield的含义先无论。该函数主要是建立两个OverlayEntry对象,一个是咱们须要展现页面所对应的OverlayEntry对象,一个是Barrier所对应的OverlayEntry对象。它两的关系以下。

route所对应的两个 OverlayEntry对象建立成功后,就加入到集合 _overlayEntries中,而后调用 OverlayStateinsertAll函数。

class OverlayState extends State<Overlay> with TickerProviderStateMixin {
  final List<OverlayEntry> _entries = <OverlayEntry>[];

  ......

  void insertAll(Iterable<OverlayEntry> entries, { OverlayEntry below, OverlayEntry above }) {
    if (entries.isEmpty)
      return;
    for (OverlayEntry entry in entries) {
      entry._overlay = this;
    }
    //更新UI
    setState(() {
      _entries.insertAll(_insertionIndex(below, above), entries);
    });
  }
  
  @override
  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,
    );
  }

  ......
  
}
复制代码

insertAll里,就能够发现页面跳转的本质了。就是经过setState来更新UI。

再来看OverlayStatebuild的函数,在该函数中会对须要保存在内存中的页面进行分类,主要有如下两类。

  • onstageChildren:在该集合中的页面会展现在屏幕中,也就是咱们可以看到的页面。
  • offstageChildren:该集合中的页面存在于内存中,不会销毁,也不会展现。主要目的是为了下次加载时,方便从新加载,从而达到节省资源的目的。当maintainState为true时就会添加到该集合,目前面跳转的maintainState参数默认为true。

来看一个示例。假设有三个页面page一、page2及page3。咱们先给page2页面的maintainState属性设置不一样值,而后观察page2跳转到page3时page2页面的变化状况。

能够发现,当maintainState为true时,会在内存中缓存page2页面的全部对象,以便下次操做。固然若是后面不须要再次展现page2,那么就最好将maintainState设为false。

三、pop页面源码解析

前面分析了在flutter中如何跳转一个新的页面,那么来看一下如何关闭一个页面。关闭页面是调用的pop函数。

bool pop<T extends Object>([ T result ]) {
    //从路由栈中获取当前页面对应的route对象
    final Route<dynamic> route = _history.last;
    bool debugPredictedWouldPop;
    if (route.didPop(result ?? route.currentResult)) {
      if (_history.length > 1) {
        //从集合中移除当前页面所对应的route对象
        _history.removeLast();
        // If route._navigator is null, the route called finalizeRoute from
        // didPop, which means the route has already been disposed and doesn't
        // need to be added to _poppedRoutes for later disposal.
        if (route._navigator != null)
          _poppedRoutes.add(route);
        _history.last.didPopNext(route);
        for (NavigatorObserver observer in widget.observers)
          observer.didPop(route, _history.last);
        RouteNotificationMessages.maybeNotifyRouteChange(_routePoppedMethod, route, _history.last);
      } else {
        return false;
      }
    } else {}
    _afterNavigation<dynamic>(route);
    return true;
  }
复制代码

这里重点是didPop这个函数。该函数中会调用NavigatorStatefinalizeRoute函数来释放资源。而finalizeRoute又会调用routedispose函数。

abstract class OverlayRoute<T> extends Route<T> {

  @override
  bool didPop(T result) {
    final bool returnValue = super.didPop(result);
    assert(returnValue);
    //释放资源操做
    if (finishedWhenPopped)
      navigator.finalizeRoute(this);
    return returnValue;
  }

  //释放资源
  @override
  void dispose() {
    //从_overlayEntries集合中移除当前页面及Barrier层
    for (OverlayEntry entry in _overlayEntries)
      entry.remove();
    _overlayEntries.clear();
    super.dispose();
  }
}
复制代码

dispose函数中,会将_overlayEntries集合中清空,这里之因此是集合并遍历,是由于一个页面对应2个OverlayEntry对象。

void remove() {
    final OverlayState overlay = _overlay;
    //取消OverlayEntry对OverlayState对象的引用
    _overlay = null;
    if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) {
      //若是当前正在渲染UI,则会等待渲染完毕后,才更新。
      SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
        //更新UI
        overlay._remove(this);
      });
    } else {
      //更新UI
      overlay._remove(this);
    }
  }
复制代码

再来看OverlayState_remove函数,该函数也是调用了setState函数。而后等待UI的刷新便可。

class OverlayState extends State<Overlay> with TickerProviderStateMixin {
  final List<OverlayEntry> _entries = <OverlayEntry>[];
  
  ......
  
  void _remove(OverlayEntry entry) {
    if (mounted) {
      setState(() {
        _entries.remove(entry);
      });
    }
  }
  
  ......
}
复制代码

再来看上一个示例,在page2页面关闭时的变化状况。

能够发现,当maintainState为false时,从page3返回page2,会从新建立page2页面的全部对象。若是page2页面比较复杂,那么此举就比较耗资源,想必这也是maintainState默认为true的缘由吧。

四、总结

固然,Navigator还有其余实现函数,如pushNamed(根据routeName跳转)、pushAndRemoveUntil(关闭并跳转到新页面)、replace(替换当前页面)等。但这些函数的具体实现跟pushpop的实现基本上一致,因此只要理解了flutter中如何进行页面跳转,那么也就基本上了解了Navigator的的实现原理。也就知道了弹窗的打开、关闭的实现原理,由于这些操做也是经过Navigator来实现的。

【参考资料】

Flutter进阶:路由、路由栈详解及案例分析

Flutter 路由原理解析

相关文章
相关标签/搜索