Flutter 必知必会系列 —— 全面认识 Route 路由
往期精彩
前面我们介绍了 Overlay
组件,Overlay
可以实现组件叠加效果。这个组件叠加效果就是我们 APP 的页面叠加效果,可以一个页面一个页面的打开。除此之外,还介绍了 Overlay
背后的无名英雄 _Theatre
,通过 _Theatre
可以仅仅布局绘制站在舞台上的内容,我们越来樾靠近 Flutter 页面管理的真相。
这一篇,我们再介绍一个前置 ———— Route。Route
出现在我们代码中的各个位置,比如我们打开的页面,就放到了 MaterialRoute
中,我们的弹窗放到了 RawDialogRoute
中,我们的底部半屏 Sheet
放到了 _ModalBottomSheetRoute
中。下面我们就看看 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.
这个类定义了一些抽象的接口,可以让 navigator 去 pop 或者 push。大多数 route 都是可视的,
因为 route 会被放置在 Overlay 中。
从上面的描述来看,Route 就是一个被 Navigator 管理的抽象接口类,这个类定义了一些抽象的接口,可以让 navigator
去 pop
或者 push
。
正式由于 Overlay
的存在,所以大多数 route
都是可视的。
从描述似乎看不出什么,我们看它封装的接口:成员变量和成员方法。
成员变量
成员变量是一个类的血肉,作为接口 Route 中的成员变量很少,仅有 3 个。
属性名 | 类型 | 作用 |
---|---|---|
_settings | RouteSettings | Route 相关的设置,比如名字、参数等 |
overlayEntries | List | 显示页面的 OverlayEntry |
navigator | NavigatorState | 承载本 Route 的 Navigator |
restorationScopeId | ValueListenable | 页面数据存放和恢复的 id |
这些变量的初始化都是在类声明的时候,所以和 Route 页面是同生共死的。Navigator
是 StatefulWidget
本身没有绘制的能力,它的 build 方法构造的子树是 Overlay
.
因为 Navigator
是 StatefulWidget
,它的 State
就是 NavigatorState
,也就是 navigator 的引用,所以 Route 是持有 NavigatorState 的,可以感知到 NavigatorState
的声明周期。
RouteSettings
是一个路由的配置,可以为路由定义名字、定义参数。比如我们想要按着名字打开页面,就是通过 RouteSettings
来构造 Route
的,同样的道理,我们可以关闭页面栈直至某个名字的 Route
。等等
这三个是属性是 Route
最重要的属性,而 restorationScopeId
是新增的属性,主要是负责页面数据的恢复,我们后面讨论。
下面我们看定义的接口方法。
成员方法
成员变量是一个类的血肉,那么成员方法就是一个类的骨骼,定义了 Route
的行为。 Navigator
通过调用这些接口实现管理的功能:初始化、打开、关闭、改变、判断等等。下面我们一一介绍:
初始化的 install
该方法是
Route
的初始化方法,当Route
被插入到 navigator 中时(push),就会先调用该方法进行初始化。通常是把
Route
的overlayEntries
添加到Overlay
中,不同的子类,会添加各自的内容,比如动画效果等等。
压入页面的 didPush
@protected
@mustCallSuper
TickerFuture didPush() {
return TickerFuture.complete()..then<void>((void _) {
if (navigator?.widget.requestFocus == true) {
navigator!.focusScopeNode.requestFocus();
}
});
}
Route
被push
之后,会先调用install
初始化,然后就会调用该方法。 默认的行为,是当Route
的内容显示到页面之后,重新获取焦点。
添加页面的 didAdd
默认的处理行为和 didPush 相同,调用的时机也是 install 之后。 只是二者的应用场景不同,
didAdd
的场景是不需要转场的Route
,Route
需要立即出现在页面上。比如当页面数据恢复的时候,会立即显示页面,重新构造的Route
就是didAdd
。
切换页面的 didReplace
默认是没有实现的,调用的时机也是在
install
之后,不同的是Route
是以切换的方式出现的。
是否关闭页面的 willPop
Navigator
的maybePop
就会调用到这里,用来判断是否去关闭 Route。一般情况下,栈中只剩下一个
Route
的时候,是不会调用Pop
,因为那样会出现黑屏。
推出页面的 didPop
Pop Route 的请求发出时,就会调用该方法。
该方法的返回值至关重要,因为它保证了动画的执行与否。如果方法返回 true,
navigator
会从历史列表移除route
,但是dispose
没有调用。
Route 完成的 didComplete
计算
pop
的异步结果
成为顶层 Route 的 didPopNext
参数中的
nextRoute
被pop
,当前的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 的关系网。
上面就是 Route 的体系网,Route 及其子类持有多个 OverlayEntry
,每一个 OverlayEntry
都会显示到页面上,比如弹窗的黑色遮罩是一个 OverlayEntry
,显示的弹窗内容又是一个 OverlayEntry
。
Route
还持有 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();
第一步:OverlayRoute
的 overlayEntries
中添加了本 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
一般是 混入 TickerProviderStateMixin
的 State
,而 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
数组的第一个 overlayEntry
的 opaque
设置为了构造方法的 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:负责当前 Route
的 push
和 pop
动画,和驱动前一个 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 方法返回的数组中,包含了两个元素_modalBarrier
和 OverlayEntry。
这里给大家讲一下: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);//第二处
}
}
第一处:根据 animation
和 secondaryAnimation
,建立起动画监听器,这里注意一下,当我们多个数据源的时候,可以使用 Listenable
的 merge
方式。这样 _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!,
);
},
),
),
),
),
),
),
),
);
},
),
),
),
),
);
}
构造的组件层级非常深,不过组件都比较简单,我们一层层来看。
第一层:第一处的 AnimatedBuilder
。AnimatedBuilder 是动画组件,想要显示动画的组件就是 child
属性,也就是 \_ModalScopeStatus
,添加的动画就是 animation
属性,就是 route
的 restorationScopeId
,我们成员变量部分介绍过它了。这一层就是为restorationScopeId
增加了动画效果,不影响实际的显示。
第二层:_ModalScopeStatus,它是一个普通的 InheritedWidget
,用来向下传递 ModalScope
的状态( Route 是谁,是不是顶层 Route,是否可以 Pop)。
第三层:Offstage。当前是否不在“舞台”上,不在舞台上的不布局绘制。也就是说 Route
的 offstage
属性是 true 的时候,Route 不绘制,默认是false的。
不过,如果页面 Hero 动画中,HeroController 控制器会用这个值来控制后一个页面的效果。我们只需要知道这个值对我们没啥影响就行了。
第四层:PageStorage,为页面中的元素,提供一个数据存放的桶。子节点可以通过 PageStorage.of
获得到数据桶。比如 ScrollPosition
就是用该特性,存储滚动的偏移量。
@protected
void saveScrollOffset() {
PageStorage.of(context.storageContext)?.writeState(context.storageContext, pixels);
}
第五层:Actions,Actions
是新增的动作————意图组件,Route
的 DismissIntent
对应的动作就是 _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 是谁吗? 就是 animation
和 secondaryAnimation
的合并。
基本的模型就是如下:
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 的初始化了,功能基本实现。
开发者只要实现 buildPage
和 buildTransitions
就可以了,屏蔽了技术细节,这可能就是模版模式的一种。
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 的初始化过程。我们知道:
- Route 中的
OverlayEntry
数组,是要添加 Navigator 的 Overlay 的,所以页面是叠加的。 - 每一层有每一层的具化。
OverlayRoute
完成了OverlayEntry
添加,TransitionRoute.install
完成了动画和动画控制器的生成。 ModalRoute.install
是集大成,生成了遮罩和动画效果的联动。buildPage
是显示的内容,buildTransitions
是动画时刻显示的内容,透传给开发者的是动画进度,大家可以根据动画进度实现自己的效果。PageRoute
是页面 Route 的父类,RawDialogRoute
是对话框的 Route,提供了默认的FadeTransition
动画。- 页面的栅栏是
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 的 push 和 pop 流程了。
转载自:https://juejin.cn/post/7075525056385777694