likes
comments
collection
share

Flutter 必知必会系列 —— 全面认识 Route 路由

作者站长头像
站长
· 阅读数 11

往期精彩

前面我们介绍了 Overlay 组件,Overlay 可以实现组件叠加效果。这个组件叠加效果就是我们 APP 的页面叠加效果,可以一个页面一个页面的打开。除此之外,还介绍了 Overlay 背后的无名英雄 _Theatre,通过 _Theatre 可以仅仅布局绘制站在舞台上的内容,我们越来樾靠近 Flutter 页面管理的真相。

这一篇,我们再介绍一个前置 ———— RouteRoute 出现在我们代码中的各个位置,比如我们打开的页面,就放到了 MaterialRoute 中,我们的弹窗放到了 RawDialogRoute 中,我们的底部半屏 Sheet 放到了 _ModalBottomSheetRoute 中。下面我们就看看 Route 的内容,相信你看完之后,会对页面有一个全新的认识。

Flutter 必知必会系列 —— 全面认识 Route 路由

Route 是什么

我们先看看官方是怎么定义 Route 的。

An abstraction for an entry managed by a [Navigator]
被Navigator管理的抽象的实体 

This class defines an abstract interface between the navigator and the routes
that are pushed on and popped off the navigator. Most routes havevisual
affordances, which they place in the navigators Overlay using oneor more
OverlayEntry objects.
这个类定义了一些抽象的接口,可以让 navigatorpop 或者 push。大多数 route 都是可视的,
因为 route 会被放置在 Overlay 中。

从上面的描述来看,Route 就是一个被 Navigator 管理的抽象接口类,这个类定义了一些抽象的接口,可以让 navigatorpop 或者 push

正式由于 Overlay 的存在,所以大多数 route 都是可视的。

从描述似乎看不出什么,我们看它封装的接口:成员变量成员方法

成员变量

成员变量是一个类的血肉,作为接口 Route 中的成员变量很少,仅有 3 个。

属性名类型作用
_settingsRouteSettingsRoute 相关的设置,比如名字、参数等
overlayEntriesList显示页面的 OverlayEntry
navigatorNavigatorState承载本 Route 的 Navigator
restorationScopeIdValueListenable页面数据存放和恢复的 id

这些变量的初始化都是在类声明的时候,所以和 Route 页面是同生共死的。NavigatorStatefulWidget 本身没有绘制的能力,它的 build 方法构造的子树是 Overlay.

因为 NavigatorStatefulWidget,它的 State 就是 NavigatorState,也就是 navigator 的引用,所以 Route 是持有 NavigatorState 的,可以感知到 NavigatorState 的声明周期。

RouteSettings 是一个路由的配置,可以为路由定义名字、定义参数。比如我们想要按着名字打开页面,就是通过 RouteSettings 来构造 Route 的,同样的道理,我们可以关闭页面栈直至某个名字的 Route。等等

这三个是属性是 Route 最重要的属性,而 restorationScopeId 是新增的属性,主要是负责页面数据的恢复,我们后面讨论。

下面我们看定义的接口方法。

成员方法

成员变量是一个类的血肉,那么成员方法就是一个类的骨骼,定义了 Route 的行为。 Navigator 通过调用这些接口实现管理的功能:初始化、打开、关闭、改变、判断等等。下面我们一一介绍:

初始化的 install

该方法是 Route 的初始化方法,当 Route 被插入到 navigator 中时(push),就会先调用该方法进行初始化。

通常是把 RouteoverlayEntries 添加到 Overlay 中,不同的子类,会添加各自的内容,比如动画效果等等。

压入页面的 didPush

@protected
@mustCallSuper
TickerFuture didPush() {
  return TickerFuture.complete()..then<void>((void _) {
    if (navigator?.widget.requestFocus == true) {
      navigator!.focusScopeNode.requestFocus();
    }
  });
}

Routepush 之后,会先调用 install 初始化,然后就会调用该方法。 默认的行为,是当 Route 的内容显示到页面之后,重新获取焦点。

添加页面的 didAdd

默认的处理行为和 didPush 相同,调用的时机也是 install 之后。 只是二者的应用场景不同,didAdd 的场景是不需要转场的 RouteRoute 需要立即出现在页面上。比如当页面数据恢复的时候,会立即显示页面,重新构造的 Route 就是 didAdd

切换页面的 didReplace

默认是没有实现的,调用的时机也是在 install 之后,不同的是 Route 是以切换的方式出现的。

是否关闭页面的 willPop

NavigatormaybePop 就会调用到这里,用来判断是否去关闭 Route

一般情况下,栈中只剩下一个 Route 的时候,是不会调用 Pop,因为那样会出现黑屏。

推出页面的 didPop

Pop Route 的请求发出时,就会调用该方法。

该方法的返回值至关重要,因为它保证了动画的执行与否。如果方法返回 true,navigator 会从历史列表移除 route,但是 dispose 没有调用。

Route 完成的 didComplete

计算 pop 的异步结果

成为顶层 Route 的 didPopNext

参数中的 nextRoutepop,当前的 Route 成为最顶层的 Route

Route 层级变化的 didChangeNext

将下一个 route 指定为 nextRoute。只要 route 的 next route 发生了改变,这个方法都要被调用。

Route 层级变化的 didChangePrevious

Route 的前一个 Route 被指定为 previousRoute,只要 Route 的前一个 Route 发生改变,这个方法都要被调用。

内部状态变化的 changedInternalState

Route 的内部状态发生变化时,就会调用该方法。

willHandlePopInternally、didPop、offstage、其他的内部值变化时,都会调用该方法。

比如 ModalRoute 使用这个方法来通知子节点信息发生改变。

外部状态变化的 changedExternalState

Navigator 重新构建时,那就表明 Route 可能也需要重新构建。比如 MaterialApp 发生重新构建的时候。

这样就保证了 Route 所依赖的构建了 MaterialApp 的 Widget 的状态发生时 就会收到通知。

Route 释放的 dispose

Route 会移除 overlays,并且释放其他的资源,不再持有 navigator 的引用

Route 的关系网

现在我们知道了 Route 是被 Navagator 用来管理页面的抽象类。内部持有的 overlayEntry 数组,并且将数组插入到 Overlay 中,实现页面叠加效果。并且定义了很多抽象方法,这些方法的设计就是面向对象的设计,作为 Route 要有这些能力。

下面我们看 Route 的关系网

Flutter 必知必会系列 —— 全面认识 Route 路由

上面就是 Route 的体系网,Route 及其子类持有多个 OverlayEntry,每一个 OverlayEntry 都会显示到页面上,比如弹窗的黑色遮罩是一个 OverlayEntry,显示的弹窗内容又是一个 OverlayEntryRoute 还持有 NavigatorState,这样就可以访问 NavigatorState 的属性和方法,比如焦点等等。

除此之外,Route 是有继承体系的,每一层实现特定的功能。 OverlayRoute 实现了将 OverlayEntry 插入到 Overlay 的功能。TransitionRoute 实现了 Route 的动画切换功能,我们看到它持有动画对象和动画控制器对象。 ModalRoute 相对比较完整了,做了返回拦截,这个拦截就是我们常用的 WillPopScope 组件。 在 ModalRoute 的基础上分为两类:显示弹窗、popWindow 类型的 PopupRoute,显示页面的 PageRoute,最下层的 Rout e就是我们开发中最常用的 Route。

下面我们通过初始化和释放来看,不同层级添加的各自的功能。

Route 线性初始化

Route 的初始化就是我们上面讲到的 install 方法,下面我们就看初始化的内容是什么、每一层是怎么实现初始化的。

Route.install

Route 是最基础的类,定义了规范。

void install() { }

我们看到,Route 中只是声明了该方法,并没有具体的实现。

OverlayRoute.install

@override
void install() {
  assert(_overlayEntries.isEmpty);
  _overlayEntries.addAll(createOverlayEntries());
  super.install();
}

@factory 
Iterable<OverlayEntry> createOverlayEntries();

第一步:OverlayRouteoverlayEntries 中添加了本 Route 所有的 OverlayEntry,可能是一个、可能是多个。

注意: createOverlayEntries 是抽象方法,非抽象的子类必须告诉 framework 要显示的内容是啥!

后面我们就知道了 createOverlayEntries 返回的都是要添加或者显示的。

TransitionRoute.install

@override
void install() {
  _controller = createAnimationController(); //第一处
  _animation = createAnimation()
    ..addStatusListener(_handleStatusChanged);//第三处
  super.install();//第四处
  if (_animation!.isCompleted && overlayEntries.isNotEmpty) {
    overlayEntries.first.opaque = opaque;
  }
}

AnimationController createAnimationController() {
  final Duration duration = transitionDuration;
  final Duration reverseDuration = reverseTransitionDuration;
  return AnimationController( //第二处
    duration: duration,
    reverseDuration: reverseDuration,
    debugLabel: debugLabel,
    vsync: navigator!,
  );
}

第一处代码创建了动画的控制器,默认就是一个动画时长是 transitionDuration 的动画控制器

默认创建的流程就是 createAnimationController,这里注意第二处的代码。

我们知道动画需要一个 vsync 参数,vsync 一般是 混入 TickerProviderStateMixinState,而 NavigatorState 就是这样的 State,所以 vsync 传入了 navigator,这也是为啥 Route 要持有 navigator 的引用的原因。

第三处的代码创建了动画对象,_animation 用来驱动页面过度动画,并且为动画添加了状态的监听。

监听的内容是:

void _handleStatusChanged(AnimationStatus status) {
  switch (status) {
    case AnimationStatus.completed:
      if (overlayEntries.isNotEmpty)
        overlayEntries.first.opaque = opaque;
      break;
    //... 省略代码
  }
}

我们以动画完成为例,这里 overlayEntries 数组的第一个 overlayEntryopaque 设置为了构造方法的 opaque

最后,大家注意第四点,TransitionRoute 先完成自己动画的初始化,才调用的 super 的内容,也就是动画的初始化先于 overlayEntry 内容的初始化。

因此:TransitionRoute 主要是创建了动画对象(当前动画的进度)和动画控制器。

ModalRoute.install

@override
void install() {
  super.install();
  _animationProxy = ProxyAnimation(super.animation);
  _secondaryAnimationProxy = ProxyAnimation(super.secondaryAnimation);
}

ModalRoute 生成了两个代理动画。

Proxy 代理动画:接收一个 Animation 类作为其父级,并仅转发该父级的状态。就是说:_animationProxy 的动画 value 就是 animation 的 value

这里讲一下为什么生成了两个动画:因为前一个页面需要退出,后一个页面需要进入。

_animationProxy:负责当前 Routepushpop 动画,和驱动前一个 Route 的动画,比如驱动前一个的 pop 动画。

_secondaryAnimationProxy:负责放到这个 Route 之上的 Route 的动画,这个动画可以让 Route 本身和新 Route 的进入和退出动画相互衔接。

这就是 install 方法,OverlayRoute.install 方法还有一个 createOverlayEntries 抽象方法,我们看看在 ModalRoute 的具化。

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

这里用了一个 Dart 语法:sync* 和 yield

sync* 和 yield 是 Dart 的一组语法关键字,sync* 放在方法签名上,表明该方法 return 一个 Iterable 数组对象。数组的元素是每一行yield 生成的。

所以上面 createOverlayEntries 方法返回的数组中,包含了两个元素_modalBarrierOverlayEntry

这里给大家讲一下:maintainState 字段,它表明 Route 的状态是否需要维持,就是我们之前讲过的在候场区的 Entry

我们知道页面是叠加的,如果一个页面没有显示,被顶层的页面 盖住了,那么它是否还需要存活在内存中呢?这就是这个字段的含义。如果是 true,Route 就会被保存,那么当前 Route 的一些 Future 结果,前一个被覆盖的 Route 就可以被正常的计算,比如刷新等等。如果设置为 false,那么在内存紧张的时候,就会回收掉。

我们熟知的 MaterialPageRoute 就是 true

下面,我们分别看添加的内容是什么。

遮罩 _modalBarrier

Widget _buildModalBarrier(BuildContext context) {
  Widget barrier;
  if (barrierColor != null && barrierColor!.alpha != 0 && !offstage) { // changedInternalState is called if barrierColor or offstage updates
    final Animation<Color?> color = animation!.drive(
      ColorTween(
        begin: barrierColor!.withOpacity(0.0),
        end: barrierColor, // changedInternalState is called if barrierColor updates
      ).chain(CurveTween(curve: barrierCurve)), // changedInternalState is called if barrierCurve updates
    );
    barrier = AnimatedModalBarrier( //第一处
      color: color,
      dismissible: barrierDismissible, // changedInternalState is called if barrierDismissible updates
      semanticsLabel: barrierLabel, // changedInternalState is called if barrierLabel updates
      barrierSemanticsDismissible: semanticsDismissible,
    );
  } else {
    barrier = ModalBarrier( //第一处
      dismissible: barrierDismissible, // changedInternalState is called if barrierDismissible updates
      semanticsLabel: barrierLabel, // changedInternalState is called if barrierLabel updates
      barrierSemanticsDismissible: semanticsDismissible,
    );
  }
  //...省略代码
  barrier = IgnorePointer(//第二处
    ignoring: animation!.status == AnimationStatus.reverse || // changedInternalState is called when animation.status updates
              animation!.status == AnimationStatus.dismissed, // dismissed is possible when doing a manual pop gesture
    child: barrier,
  );
  return barrier;
}

barrierColor 就是我们经常在对话框中看到的遮罩色。

barrierDismissible 就是我们点击遮罩是否消失弹窗。

我们看第一处的代码:不管动画与否核心就是 ModalBarrier,也就是说 Route 里面第一个是 ModalBarrier

ModalBarrier 的作用就是三个:增加遮罩背景,点击遮罩回退页面,阻止事件穿透。

我们在看第二处的代码:IgnorePointer 组件,IgnorePointer 可以拦截事件,ignoring 属性是true 的时候,barrier 不能响应手势,所以我们可以理解为 对话框展示完成了,周边的黑色遮罩是可以响应手势的。

其实加这一层,主要是为了阻止手势传递给下一层的 页面。

实际内容 _buildModalScope

Widget _buildModalScope(BuildContext context) {
  // To be sorted before the _modalBarrier.
  return _modalScopeCache ??= Semantics(
    sortKey: const OrdinalSortKey(0.0),
    child: _ModalScope<T>(
      key: _scopeKey,
      route: this,
      // _ModalScope calls buildTransitions() and buildChild(), defined above
    ),
  );
}

我们再看 _ModalScope。 _ModalScope 是 StatefulWidget,我们可以用 StatefulWidget 去理解它。

先看 initState 方法

@override
void initState() {
  super.initState();
  final List<Listenable> animations = <Listenable>[
    if (widget.route.animation != null) widget.route.animation!,
    if (widget.route.secondaryAnimation != null) widget.route.secondaryAnimation!,
  ];
  _listenable = Listenable.merge(animations); //第一处
  if (widget.route.isCurrent && _shouldRequestFocus) {
    widget.route.navigator!.focusScopeNode.setFirstFocus(focusScopeNode);//第二处
  }
}

第一处:根据 animationsecondaryAnimation,建立起动画监听器,这里注意一下,当我们多个数据源的时候,可以使用 Listenablemerge 方式。这样 _listenable 就可以:即会响应 animation 的变化,也会响应 secondaryAnimation 的变化。

第二处:焦点作用域移到当前的范围

关于 focusScopeNode 大家可以看这一篇文章:说说Flutter中的无名英雄 —— Focus

我们再看 build 方法,这是重头戏。

@override
Widget build(BuildContext context) {
  return AnimatedBuilder( //第一处
    animation: widget.route.restorationScopeId,
    builder: (BuildContext context, Widget? child) {
      return RestorationScope(
        restorationId: widget.route.restorationScopeId.value,
        child: child!,
      );
    },
    child: _ModalScopeStatus(
      route: widget.route,
      isCurrent: widget.route.isCurrent, // _routeSetState is called if this updates
      canPop: widget.route.canPop, // _routeSetState is called if this updates
      child: Offstage(
        offstage: widget.route.offstage, // _routeSetState is called if this updates
        child: PageStorage(
          bucket: widget.route._storageBucket, // immutable
          child: Builder(
            builder: (BuildContext context) {
              return Actions(
                actions: <Type, Action<Intent>>{
                  DismissIntent: _DismissModalAction(context),
                },
                child: PrimaryScrollController(
                  controller: primaryScrollController,
                  child: FocusScope(
                    node: focusScopeNode, // immutable
                    child: FocusTrap(
                      focusScopeNode: focusScopeNode,
                      child: RepaintBoundary(
                        child: AnimatedBuilder(
                          animation: _listenable, // immutable
                          builder: (BuildContext context, Widget? child) {
                            return widget.route.buildTransitions(
                              context,
                              widget.route.animation!,
                              widget.route.secondaryAnimation!,
                              AnimatedBuilder(
                                animation: widget.route.navigator?.userGestureInProgressNotifier ?? ValueNotifier<bool>(false),
                                builder: (BuildContext context, Widget? child) {
                                  final bool ignoreEvents = _shouldIgnoreFocusRequest;
                                  focusScopeNode.canRequestFocus = !ignoreEvents;
                                  return IgnorePointer(
                                    ignoring: ignoreEvents,
                                    child: child,
                                  );
                                },
                                child: child,
                              ),
                            );
                          },
                          child: _page ??= RepaintBoundary(
                            key: widget.route._subtreeKey, // immutable
                            child: Builder(
                              builder: (BuildContext context) {
                                return widget.route.buildPage(
                                  context,
                                  widget.route.animation!,
                                  widget.route.secondaryAnimation!,
                                );
                              },
                            ),
                          ),
                        ),
                      ),
                    ),
                  ),
                ),
              );
            },
          ),
        ),
      ),
    ),
  );
}

构造的组件层级非常深,不过组件都比较简单,我们一层层来看。

第一层:第一处的 AnimatedBuilderAnimatedBuilder 是动画组件,想要显示动画的组件就是 child 属性,也就是 \_ModalScopeStatus,添加的动画就是 animation 属性,就是 routerestorationScopeId,我们成员变量部分介绍过它了。这一层就是为restorationScopeId 增加了动画效果,不影响实际的显示。

第二层:_ModalScopeStatus,它是一个普通的 InheritedWidget,用来向下传递 ModalScope 的状态( Route 是谁,是不是顶层 Route,是否可以 Pop)。

第三层:Offstage。当前是否不在“舞台”上,不在舞台上的不布局绘制。也就是说 Routeoffstage 属性是 true 的时候,Route 不绘制,默认是false的。 不过,如果页面 Hero 动画中,HeroController 控制器会用这个值来控制后一个页面的效果。我们只需要知道这个值对我们没啥影响就行了。

第四层:PageStorage,为页面中的元素,提供一个数据存放的桶。子节点可以通过 PageStorage.of 获得到数据桶。比如 ScrollPosition 就是用该特性,存储滚动的偏移量。

@protected 
void saveScrollOffset() { 
   PageStorage.of(context.storageContext)?.writeState(context.storageContext, pixels); 
}

第五层:ActionsActions 是新增的动作————意图组件RouteDismissIntent 对应的动作就是 _DismissModalAction,会执行 Navigator.of(context).maybePop() 行为。不过这个添加对我们移动端并没有太多影响。

第六层:PrimaryScrollController,是默认的滚动控制器,这也就是为什么我们即使不手动为 ListView 添加 ScrollController,依旧有一个ScrollController 可用的原因。

第七层:FocusScope,为页面添加了一个焦点域,并提供了一个焦点。

第八层:FocusTrap,这是为 web 准备的组件,和我们移动端关系不大。

第九层:RepaintBoundary,为页面添加绘制的边界,也就是说这个节点以上的节点是不需要绘制的。

同样的道理,绘制也是需要边界的。如果一个节点需要被绘制,那么它会看 isRepaintBoundary 值,这个值是 true 的话,就会仅仅绘制自己,就不向上去看父节点的绘制情况。 RepaintBoundary 组件的 isRepaintBoundary 值就是 true。

前面的几层都是一个页面的准备工作,下面的第十层就是真正复杂的地方

第十层依然是一个 AnimatedBuilder 动画组件,这个动画组件,就是给我们 Route 增加了动画效果。AnimatedBuilder 就是给构造方法的 child 参数,增加了一个参数的 builder 效果。动画的驱动器是参数 _listenable。还记得 _listenable 是谁吗? 就是 animationsecondaryAnimation 的合并。

基本的模型就是如下:

AnimatedBuilder(
  animation: _listenable, 
  builder: (BuildContext context, Widget child) {
    return B;
  },
  child: A,
)

我们可以认为,当 _listenable 的值(也就是动画的值)发生改变时,页面会显示 B。并且 builder 的 child 参数就是 A。

现在先看 A 是谁。

RepaintBoundary(
  key: widget.route._subtreeKey, // immutable
  child: Builder(
    builder: (BuildContext context) {
      return widget.route.buildPage(
        context,
        widget.route.animation!,
        widget.route.secondaryAnimation!,
      );
    },
  ),
)

A 是 Builder 组件,所以 A 就是 Route 的 buildPage 方法构造的 Widget。

同理,B 中参数的 child 就是 buildPage 方法的结果。我们看 B 是谁。

widget.route.buildTransitions(
  context,
  widget.route.animation!,
  widget.route.secondaryAnimation!,
  AnimatedBuilder(
    animation: widget.route.navigator?.userGestureInProgressNotifier ?? ValueNotifier<bool>(false),
    builder: (BuildContext context, Widget? child) {
      final bool ignoreEvents = _shouldIgnoreFocusRequest;
      focusScopeNode.canRequestFocus = !ignoreEvents;
      return IgnorePointer(
        ignoring: ignoreEvents,
        child: child,
      );
    },
    child: child,
  ),
)

B 就是 Route 的 buildTransitions 构建的 Widget

B 中又出现了 AnimatedBuilder。这个动画不会影响页面的渲染,只是根据 route 的动画状态,来决定屏不屏蔽手势,用于显示的还是 build。

到这里我们知道了,第十层用到了动画机制,用于动画的组件是 Route 的 buildPage 方法,具体的动画是什么 Route 的 buildTransitions 方法。

比如当动画是 0 的时候,我们让 buildTransitions 返回 child,页面显示的就是 Route 的 buildPage。 动画是 0.5 的时候,我们让 buildTransitions 返回 Text 文本组件,页面显示的就是 Text 文本组件。 动画是 1 的时候,我们让 buildTransitions 返回 child,页面显示的仍然就是 Route 的 buildPage 了。 所以根据这个特性,我们就可以显示出平移、缩放等效果了。

至此,ModalRoute.install 就初始化完了,构造了两个 OverlayEntry。 一个是遮罩,处理了颜色、点击返回等。 一个是 _ModalScope,共十几个节点:实现了基本的传递 Modal 的 Route 信息,焦点,绘制不绘制、数据桶、绘制边界、动画等。

ModalRoute 的初始化,基本就是 Route 的初始化了,功能基本实现。 开发者只要实现 buildPagebuildTransitions 就可以了,屏蔽了技术细节,这可能就是模版模式的一种。

buildPage 是用于页面显示的内容,buildTransitions 是内容的添加动画效果的显示。

RawDialogRoute.install

上面的 ModalRoute 基本已经完成了主要功能,并且明确了子类的继承任务,重写 buildPage 确定显示内容,重写 buildTransitions 确定过渡动画。

作为具体的对话框的 Route,它仅仅需要实现这两个方法,我们以动画为例:

@override
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
  if (_transitionBuilder == null) {
    return FadeTransition(
      opacity: CurvedAnimation(
        parent: animation,
        curve: Curves.linear,
      ),
      child: child,
    );
  } // Some default transition
  return _transitionBuilder!(context, animation, secondaryAnimation, child);
}

我们看到 buildTransitions 中定义了对话框的默认动画效果 —— 线性渐显动画

小结

我们从顶到具体的阅读,看了每一层 Route 的初始化过程。我们知道:

  1. Route 中的 OverlayEntry 数组,是要添加 Navigator 的 Overlay 的,所以页面是叠加的。
  2. 每一层有每一层的具化。OverlayRoute 完成了 OverlayEntry 添加,TransitionRoute.install 完成了动画和动画控制器的生成。
  3. ModalRoute.install 是集大成,生成了遮罩和动画效果的联动。buildPage 是显示的内容,buildTransitions 是动画时刻显示的内容,透传给开发者的是动画进度,大家可以根据动画进度实现自己的效果。
  4. PageRoute 是页面 Route 的父类,RawDialogRoute 是对话框的 Route,提供了默认的 FadeTransition 动画。
  5. 页面的栅栏是 true 的,所以不会布局和绘制被遮挡的页面。

Route 线性释放

看了线性的初始化,我们再看线性的释放资源。释放资源就是 dispose 方法。相比与初始化,释放更加简单,基本就是申请了啥,就释放啥

Route.dispose

void dispose() {
  _navigator = null;
}

不再引用 navigator,避免内存泄漏。

OverlayRoute.dispose

@override
void dispose() {
  _overlayEntries.clear();
  super.dispose();
}

与 OverlayRoute 的初始化相对应,OverlayRoute 的初始化中,通过 createOverlayEntries 构造了 OverlayEntry

因此,dispose 中就 移除了 OverlayEntry

TransitionRoute.dispose

@override
void dispose() {
   _animation?.removeStatusListener(_handleStatusChanged);
  if (willDisposeAnimationController) {
    _controller?.dispose();
  }
  _transitionCompleter.complete(_result);
  super.dispose();
}

TransitionRoute 的初始化相对应,TransitionRoute 构造了动画控制器 Controller,因此 dispose 就将控制器释放掉

至此,初始化保持的资源已经释放完毕了,后面的也就不需要再次调用了。

总结

至此,我们已经基本明白了 Route 是什么,并且对 Route 的层级体系和结构有了一定的了解。对我们开发者来说,我们只需要知道:

  • 页面是有栅栏的,不需要担心非顶层页面的绘制问题
  • buildPage 中显示我们想要的内容,在 buildTransitions 中根据动画进度,实现自己的效果。

在本节的基础上,后面我们就可以更加透彻的看 Route 的 pushpop 流程了。