博主相关文章列表
Flutter 框架实现原理
Flutter 框架层启动源码剖析
Flutter 页面更新流程剖析
Flutter 事件处理源码剖析
Flutter 路由源码剖析
Flutter 安卓平台源码剖析(一)
Flutter 自定义控件之RenderObject
路由页面的简单树形结构,仅包含关键控件
我们知道MaterialApp
实际上是对WidgetsApp
的包装,而WidgetsApp
是一个有状态的Widget,这里查看它的State.build
实现,主要是构建了Navigator
flutter\lib\src\widgets\app.dart
/// [_WidgetsAppState]
Widget build(BuildContext context) {
Widget navigator;
if (_navigator != null) {
// 构建 Navigator
navigator = Navigator(
key: _navigator,
// 如果window.defaultRouteName不是'/',我们应该假设它是通过`setInitialRoute`有意设置的,
// 并且应该覆盖[widget.initialRoute]中的内容。
initialRoute: WidgetsBinding.instance.window.defaultRouteName != Navigator.defaultRouteName
? WidgetsBinding.instance.window.defaultRouteName
: widget.initialRoute ?? WidgetsBinding.instance.window.defaultRouteName,
onGenerateRoute: _onGenerateRoute,
onGenerateInitialRoutes: widget.onGenerateInitialRoutes == null
? Navigator.defaultGenerateInitialRoutes
: (NavigatorState navigator, String initialRouteName) {
return widget.onGenerateInitialRoutes(initialRouteName);
},
onUnknownRoute: _onUnknownRoute,
observers: widget.navigatorObservers,
);
}
Widget result;
if (widget.builder != null) {
result = Builder(
builder: (BuildContext context) {
return widget.builder(context, navigator);
},
);
} else {
assert(navigator != null);
result = navigator;
}
if (widget.textStyle != null) {
result = DefaultTextStyle(
style: widget.textStyle,
child: result,
);
}
PerformanceOverlay performanceOverlay;
// 如果设置了任何显示或checkerboarding选项,则需要推送性能叠加层
if (widget.showPerformanceOverlay || WidgetsApp.showPerformanceOverlayOverride) {
performanceOverlay = PerformanceOverlay.allEnabled(
checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
);
} else if (widget.checkerboardRasterCacheImages || widget.checkerboardOffscreenLayers) {
performanceOverlay = PerformanceOverlay(
checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
);
}
if (performanceOverlay != null) {
result = Stack(
children: [
result,
Positioned(top: 0.0, left: 0.0, right: 0.0, child: performanceOverlay),
],
);
}
if (widget.showSemanticsDebugger) {
result = SemanticsDebugger(
child: result,
);
}
Widget title;
if (widget.onGenerateTitle != null) {
title = Builder(
// 该Builder的存在是为了在Localizations小部件下面提供上下文
// onGenerateTitle回调可以通过其context参数引用Localizations
builder: (BuildContext context) {
final String title = widget.onGenerateTitle(context);
return Title(
title: title,
color: widget.color,
child: result,
);
},
);
} else {
title = Title(
title: widget.title,
color: widget.color,
child: result,
);
}
final Locale appLocale = widget.locale != null
? _resolveLocales([widget.locale], widget.supportedLocales)
: _locale;
return Shortcuts(
shortcuts: widget.shortcuts ?? WidgetsApp.defaultShortcuts,
debugLabel: '',
child: Actions(
actions: widget.actions ?? WidgetsApp.defaultActions,
child: FocusTraversalGroup(
policy: ReadingOrderTraversalPolicy(),
child: _MediaQueryFromWindow(
child: Localizations(
locale: appLocale,
delegates: _localizationsDelegates.toList(),
child: title,
),
),
),
),
);
}
Navigator
也是有状态Widget,这里查看NavigatorState
实现
flutter\lib\src\widgets\navigator.dart
/// [NavigatorState]
List<_RouteEntry> _history = <_RouteEntry>[];
void initState() {
super.initState();
for (final NavigatorObserver observer in widget.observers) {
observer._navigator = this;
}
String initialRoute = widget.initialRoute;
if (widget.pages.isNotEmpty) {
_history.addAll(
widget.pages.map((Page page) => _RouteEntry(
page.createRoute(context),
initialState: _RouteLifecycle.add,
))
);
} else {
// 如果没有提供页面,我们将需要提供默认路由来初始化导航器
initialRoute = initialRoute ?? Navigator.defaultRouteName;
}
if (initialRoute != null) {
// 此处创建根路由,并添加到_history中保存
_history.addAll(
widget.onGenerateInitialRoutes(
this,
widget.initialRoute ?? Navigator.defaultRouteName
).map((Route route) =>
_RouteEntry(
route,
initialState: _RouteLifecycle.add,
),
),
);
}
// 刷新路由栈
_flushHistoryUpdates();
}
void _flushHistoryUpdates({bool rearrangeOverlay = true}) {
// 清理列表,给改变的路由发送更新
int index = _history.length - 1;
_RouteEntry next;
_RouteEntry entry = _history[index];
_RouteEntry previous = index > 0 ? _history[index - 1] : null;
bool canRemoveOrAdd = false; // 是否在上面有一个完全不透明的路由,在下面悄悄地删除或添加路由
Route poppedRoute; // 应该在最上面的活动路由上触发didPopNext的路由
bool seenTopActiveRoute = false; // 我们是否已经看过将获得didPopNext的路由
final List<_RouteEntry> toBeDisposed = <_RouteEntry>[];
while (index >= 0) {
switch (entry.currentState) {
case _RouteLifecycle.add:
entry.handleAdd(
navigator: this,
);
continue;
case _RouteLifecycle.adding:
if (canRemoveOrAdd || next == null) {
entry.didAdd(
navigator: this,
previous: previous?.route,
previousPresent: _getRouteBefore(index - 1, _RouteEntry.isPresentPredicate)?.route,
isNewFirst: next == null
);
continue;
}
break;
case _RouteLifecycle.push:
case _RouteLifecycle.pushReplace:
case _RouteLifecycle.replace:
entry.handlePush(
navigator: this,
previous: previous?.route,
previousPresent: _getRouteBefore(index - 1, _RouteEntry.isPresentPredicate)?.route,
isNewFirst: next == null,
);
if (entry.currentState == _RouteLifecycle.idle) {
continue;
}
break;
case _RouteLifecycle.pushing: // 动画完成时将退出此状态。
if (!seenTopActiveRoute && poppedRoute != null)
entry.handleDidPopNext(poppedRoute);
seenTopActiveRoute = true;
break;
case _RouteLifecycle.idle:
if (!seenTopActiveRoute && poppedRoute != null)
entry.handleDidPopNext(poppedRoute);
seenTopActiveRoute = true;
// 该路由是空闲的,因此我们可以删除等待静默删除的后续(较早)路由:
canRemoveOrAdd = true;
break;
case _RouteLifecycle.pop:
if (!seenTopActiveRoute) {
if (poppedRoute != null)
entry.handleDidPopNext(poppedRoute);
poppedRoute = entry.route;
}
entry.handlePop(
navigator: this,
previousPresent: _getRouteBefore(index, _RouteEntry.willBePresentPredicate)?.route,
);
canRemoveOrAdd = true;
break;
case _RouteLifecycle.popping:
// 动画完成时将退出此状态
break;
case _RouteLifecycle.remove:
if (!seenTopActiveRoute) {
if (poppedRoute != null)
entry.route.didPopNext(poppedRoute);
poppedRoute = null;
}
entry.handleRemoval(
navigator: this,
previousPresent: _getRouteBefore(index, _RouteEntry.willBePresentPredicate)?.route,
);
continue;
case _RouteLifecycle.removing:
if (!canRemoveOrAdd && next != null) {
// 我们还不允许删除此路由
break;
}
entry.currentState = _RouteLifecycle.dispose;
continue;
case _RouteLifecycle.dispose:
// 延迟处理,直到didChangeNext/idChangePrevious被发送
toBeDisposed.add(_history.removeAt(index));
entry = next;
break;
case _RouteLifecycle.disposed:
case _RouteLifecycle.staging:
assert(false);
break;
}
index -= 1;
next = entry;
entry = previous;
previous = index > 0 ? _history[index - 1] : null;
}
// 现在列表很干净了,发送didChangeNext / didChangePrevious通知
_flushRouteAnnouncement();
// 宣布更改路由名称。
final _RouteEntry lastEntry = _history.lastWhere(_RouteEntry.isPresentPredicate, orElse: () => null);
final String routeName = lastEntry?.route?.settings?.name;
if (routeName != _lastAnnouncedRouteName) {
RouteNotificationMessages.maybeNotifyRouteChange(routeName, _lastAnnouncedRouteName);
_lastAnnouncedRouteName = routeName;
}
// 最后,删除所有被标记的overlay实体对象并调用dispose。
for (final _RouteEntry entry in toBeDisposed) {
for (final OverlayEntry overlayEntry in entry.route.overlayEntries)
overlayEntry.remove();
entry.dispose();
}
if (rearrangeOverlay)
overlay?.rearrange(_allRouteOverlayEntries);
}
Widget build(BuildContext context) {
return Listener(
onPointerDown: _handlePointerDown,
onPointerUp: _handlePointerUpOrCancel,
onPointerCancel: _handlePointerUpOrCancel,
child: AbsorbPointer(
absorbing: false, // 它直接被上面的_cancelActivePointers方法修改
child: FocusScope(
node: focusScopeNode,
autofocus: true,
child: Overlay(
key: _overlayKey,
initialEntries: overlay == null ? _allRouteOverlayEntries.toList(growable: false) : const [],
),
),
),
);
}
// 通过生成器迭代_history列表,返回根路由包含的OverlayEntry对象
Iterable get _allRouteOverlayEntries sync* {
for (final _RouteEntry entry in _history)
yield* entry.route.overlayEntries;
}
可以看到,较为复杂的是_flushHistoryUpdates
方法,该方法中根据不同的路由生命期做了相应处理。_RouteLifecycle
是一个枚举:
enum _RouteLifecycle {
staging, // 我们将等待transition delegate决定如何处理此路由
//
// 存在的路由:
//
add, // 我们要运行install、didAdd等;由onGenerateInitialRoutes或初始widget.pages创建的路由。
adding, // 同上
// 准备好过渡的路由。
push, // 我们将要运行install,didPush等; 通过push()和朋友添加的路线
pushReplace,
pushing, // 我们正在等待didPush的future完成
replace, //
idle, // 路由是无害的
//
// 不存在的路由:
//
// 应该包含在路由公告中的路由,并且仍应侦听过渡变化
pop, // 我们要调用didPop
remove, // 我们要运行didReplace/didRemove等操作
// 路不应包含在路公告中,但仍应监听过渡变化。
popping, // 我们正在等待路由调用finalizeRoute来切换处理
removing, // 我们正在等待后续的路由完成动画制作,然后再切换到dispose
// 已从navigator和overlay中完全删除的路由
dispose, // 我们会在短时间内处理好此路由
disposed, // 我们已经处理好了路由
}
官方给出了一个 _RouteLifecycle
状态机(仅向下)的图示:
[创建一个_RouteEntry]
|
+
|\
| \
| staging
| /
|/
+-+----------+--+-------+
/ | | |
/ | | |
/ | | |
/ | | |
/ | | |
pushReplace push* add* replace*
\ | | |
\ | | /
+--pushing# adding /
\ / /
\ / /
idle--+-----+
/ \
/ \
pop* remove*
/ \
/ removing#
popping# |
| |
[finalizeRoute] |
\ |
dispose*
|
|
disposed
|
|
[_RouteEntry垃圾回收]
(终端状态)
*
:表示这些状态是短暂的;一旦运行_flushHistoryUpdates
,路由条目就会退出该状态。
#
:表示这些状态等待future或其他事件,然后自动转换。
此处初始化根路由时,设置的状态是_RouteLifecycle.add
,因此执行_RouteEntry
的handleAdd
方法
void handleAdd({ @required NavigatorState navigator}) {
route._navigator = navigator;
route.install();
currentState = _RouteLifecycle.adding;
}
这里最主要的是调用了路由的install()
方法,将根路由转换为OverlayEntry
对象,并插入到Overlay
管理的页面栈中
/// [OverlayRoute]
void install() {
_overlayEntries.addAll(createOverlayEntries());
super.install();
}
/// [ModalRoute]
// 这里直接返回了一个Iterable,它包含两个OverlayEntry对象,被添加到了_overlayEntries中
Iterable createOverlayEntries() sync* {
// 这是一个模态屏障控件,阻止用户与自己身后的控件交互(即拦截了事件传递)
yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier);
// 为根路由创建 OverlayEntry
yield OverlayEntry(builder: _buildModalScope, maintainState: maintainState);
}
NavigatorState
中的build
方法则主要构建了Overlay
控件,继续查看OverlayState
实现逻辑
flutter\lib\src\widgets\overlay.dart
/// [OverlayState]
void initState() {
super.initState();
insertAll(widget.initialEntries);
}
void insertAll(Iterable entries, { OverlayEntry below, OverlayEntry above }) {
if (entries.isEmpty)
return;
for (final OverlayEntry entry in entries) {
entry._overlay = this;
}
setState(() {
_entries.insertAll(_insertionIndex(below, above), entries);
});
}
Widget build(BuildContext context) {
// 这个列表是倒着填的,然后在下面倒过来再加到树上
final List children = [];
bool onstage = true;
int onstageCount = 0;
for (int i = _entries.length - 1; i >= 0; i -= 1) {
final OverlayEntry entry = _entries[i];
if (onstage) {
onstageCount += 1;
children.add(_OverlayEntryWidget(
key: entry._key,
entry: entry,
));
if (entry.opaque)
onstage = false;
} else if (entry.maintainState) {
children.add(_OverlayEntryWidget(
key: entry._key,
entry: entry,
tickerEnabled: false,
));
}
}
return _Theatre(
skipCount: children.length - onstageCount,
children: children.reversed.toList(growable: false),
);
}
从build
方法中可知,这里遍历OverlayEntry
列表,将保存的实体信息对象封装为_OverlayEntryWidget
控件,最终将包含_OverlayEntryWidget
的列表交给_Theatre
控件插入控件树中用于渲染。
这里_Theatre
控件是一个特殊版本的Stack
,它不对第一个skipCount
子级进行布局和渲染。第一个skipCount
子级被认为是 offstage
。
_Theatre
控件的名称很有意思,翻译过来是剧院的意思,它将页面分为了两种,一种是舞台上的(onstage
)演员,另一种则是舞台下的(offstage
)观众。当某个包装页面的OverlayEntry
的opaque
属性为true时,表示占满全屏且不透明,那么以它为分界线,它之下的所有页面都不需要绘制了(因为被挡住了看不见)。如果OverlayEntry
的maintainState
属性也为true,则被分到舞台下的观众那一组,否则,连进入剧院的资格也没有。
以上是根路由的整个初始化流程,包含路由的创建,路由入栈,路由转化等,需要注意的是,这里涉及的栈有两个,一个是保存路由的栈,一个是保存页面的栈,分别是
NavigatorState
中的List<_RouteEntry> _history = <_RouteEntry>[]
,保存路由OverlayState
中的List _entries = []
,保存用于构建页面Widget的数据结构虽然Navigator
包含一些静态方法操作路由栈,但最终调用的是仍然是NavigatorState
中的操作方法。这就是为什么需要传一个context
的原因,Navigator
需要从上下文中查找一个NavigatorState
。这里我们直接查看NavigatorState
中的push
实现
/// [NavigatorState]
Future push(Route route) {
// 使用_RouteEntry包装传进来的路由然后入栈,并将路由状态设为_RouteLifecycle.push
_history.add(_RouteEntry(route, initialState: _RouteLifecycle.push));
// 刷新路由栈
_flushHistoryUpdates();
// 用于一些调试,在非release模式下执行
_afterNavigation(route);
return route.popped;
}
_flushHistoryUpdates
方法上面已经剖析过,此处进入case
为 _RouteLifecycle.push
的分支,执行_RouteEntry
的handlePush
方法
void handlePush({ @required NavigatorState navigator, @required bool isNewFirst, @required Route previous, @required Route previousPresent }) {
final _RouteLifecycle previousState = currentState;
route._navigator = navigator;
// 调用路由的install方法进行转换
route.install();
if (currentState == _RouteLifecycle.push || currentState == _RouteLifecycle.pushReplace) {
// 推送转换完成后,返回的值将解析
final TickerFuture routeFuture = route.didPush();
// 置为 pushing状态
currentState = _RouteLifecycle.pushing;
routeFuture.whenCompleteOrCancel(() {
if (currentState == _RouteLifecycle.pushing) {
// 置为 idle状态
currentState = _RouteLifecycle.idle;
// 以idle状态再次进入路由栈更新方法
navigator._flushHistoryUpdates();
}
});
} else {
route.didReplace(previous);
currentState = _RouteLifecycle.idle;
}
if (isNewFirst) {
route.didChangeNext(null);
}
if (previousState == _RouteLifecycle.replace || previousState == _RouteLifecycle.pushReplace) {
for (final NavigatorObserver observer in navigator.widget.observers)
observer.didReplace(newRoute: route, oldRoute: previous);
} else {
// 遍历路由监听器,回调 didPush方法
for (final NavigatorObserver observer in navigator.widget.observers)
observer.didPush(route, previousPresent);
}
}
pop
与push
的流程类似
void pop([ T result ]) {
final _RouteEntry entry = _history.lastWhere(_RouteEntry.isPresentPredicate);
if (entry.hasPage) {
if (widget.onPopPage(entry.route, result))
entry.currentState = _RouteLifecycle.pop;
} else {
// 修改路由状态为_RouteLifecycle.pop
entry.pop(result);
}
if (entry.currentState == _RouteLifecycle.pop) {
// 如果路由真的要被弹出(弹出不是内部处理的),则刷新路由栈
_flushHistoryUpdates(rearrangeOverlay: false);
}
_afterNavigation(entry.route);
}
导航器,实际上它是一个用堆栈规则来管理子控件的一个控件。结合前面的知识,它主要用于管理我们的路由栈。
移动应用程序通常通过全屏元素显示其内容,称为 "屏幕 "或 “页面”。在Flutter中,这些元素被称为路由,它们由Navigator管理。导航器管理Route对象的堆栈,并提供了两种管理堆栈的方式,
Navigator.pages
Navigator.push
和Navigator.pop
如果提供了Navigator.pages
,Navigator
将把它的Navigator.pages
转换为Routes的堆栈。Navigator.pages
的变化将触发路由堆栈的更新。Navigator
将更新它的Routes以匹配Navigator.pages
的新配置。要使用这个API,可以使用CustomBuilderPage或创建一个Page子类,并为Navigator.pages定义一个Pages列表。此外,还需要一个Navigator.onPopPage回调,以便在弹出时正确清理输入页面。
显然的,在WidgetsApp
的封装中,并未使用这种声明式API的方式管理页面栈。
在Navigator
类中,持有一个路由监听器NavigatorObserver
的列表,用于在适当的时候回调监听器,通知当前路由栈的操作状态。在NavigatorState
中,则持有一个List<_RouteEntry>
类型的_history
列表,存放的是经过封装的历史路由页面。_RouteEntry
是对路由的包装类,可以为TransitionDelegate
暂存,以决定其基础Route
如何在屏幕上或屏幕外过渡。
还有一个重点需要关注,在NavigatorState
中持有一个OverlayState
的引用。
OverlayState get overlay => _overlayKey.currentState;
在NavigatorState
的build
方法中创建了一个Overlay
,它是一个有状态Widget,还包含一个OverlayState
,那么这个Overlay
及其OverlayState
有什么用呢?
查看 UML大图
Overlay
可以翻译为叠加层或覆盖层。按照官方的解释,它是一个可以独立管理的覆盖层堆栈。简单说,它才是我们页面栈的真正实现。它的内部持有一个List
类型的initialEntries
列表,但是这个列表仅包含页面栈初始化时所包含的页面,只是在OverlayState
被初始化时使用,见OverlayState
的initState
方法,而真正的页面栈储存在OverlayState
的_entries
列表中。
final List _entries = [];
在OverlayState
中,仅暴露了三个可用于修改页面栈的方法
到这里,相信大家已经彻底明白在Flutter中使用Overlay
去显示悬浮框的原理,我们使用 Overlay.of()查找到当前Navigator
持有的OverlayState
对象, 然后在页面栈中插入一层Widget显示到栈顶部。
查看 UML大图
OverlayEntry
只是一个普通类,或者说是一个数据结构,是对需要显示的页面Widget的一个实体数据包装。
默认情况下,如果在这个实体对象上层有一个完全不透明的实体对象,那么这个实体对象所代表的Widget将不会被包含在控件树中(特别是,实体对象中包装的有状态Widget将不会被实例化)。要确保实体对象包装的Widget即使不可见也仍然被构建,需要将 maintainState
设置为 true。这是比较耗费性能的,应谨慎操作。特别是,如果实体对象包装的Widget在 maintainState
设置为 true 的情况下还反复调用 State.setState
,将会增加手机电量的消耗。
OverlayEntry
的两个关键属性
maintainState
这个实体对象包装的Widget是否必须包含在控件树中,即使在它上面有一个完全不透明的实体。Navigator
和Route
对象使用它来确保即使在后台也保留路由,这样当后续路由完成时,来自后续路由的Future将被正确处理。
opaque
是否是全屏不透明。如果某个实体对象声称是不透明的,则出于效率考虑,除非设置了maintainState
为ture,否则叠加层会跳过位于该实体对象下面的所有实体对象。既然是不透明的,那么它下面的页面也看不见,于是干脆就不去绘制了。
我们知道,对话框本身也是路由,但通常不会占满全屏,而是四周呈现半透明效果,它就是一种opaque
为false的情况。
这里Route
作为一个抽象的基类,下面的每一层路由类都处理了相关的逻辑:
OverlayRoute
:在导航器的Overlay中显示控件的路由。主要将路由转换为Widget插入控件树。
TransitionRoute
:具有进入和退出过渡动画的路由。主要处理路由过渡动效。
ModalRoute
:阻止与下层路由交互的路由。它覆盖整个导航器。但它们不一定是不透明的。例如一个对话框。主要处理事件的拦截
PageRoute
:替换整个屏幕的模态路由。由它派生出了我们熟悉的MaterialPageRoute
,主要用于Flutter的页面切换。
PopupRoute
:在当前路由上覆盖Widget的模态路由。主要用于弹出框,对话框之类。
查看 Route UML大图
Flutter中使用Overlay
操作页面栈可以插入悬浮框,通过Navigator
操作路由栈可以创建对话框或弹出框。但有时候普通对话框并不能满足我们的需求,这是因为对话框由两层构成,背后还有一层蒙层,拦截事件的传递,而Overlay
悬浮框控制起来较为麻烦,不能响应返回键退栈,且永远在最顶层,无法被新页面覆盖。
此时,结合我们前面研究路由源码的心得,我们完全可以自定义路由,实现我们想要的效果。譬如,我们不希望有一个背景蒙层阻断事件,我们需要的只是一个小巧的悬浮控件,而不是覆盖全屏,这时可以自定义类继承OverlayRoute
,因为只有继承OverlayRoute
或者TransitionRoute
才能跳过背景蒙层的处理逻辑,为了简单,不想处理动画相关问题,直接使用OverlayRoute
class MyPopupRoute extends OverlayRoute{
// 实现该方法将路由转化为OverlayEntry对象返回
@override
Iterable createOverlayEntries() sync*{
yield OverlayEntry(builder: (ctx){
return Material(
child: Align(
alignment: Alignment.center,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
),
);
});
}
}
弹出悬浮控件,会在屏幕中心显示一个蓝色正方形,返回键可消除,新启页面亦能覆盖悬浮框,悬浮框下层页面中的按钮亦能响应点击。
onTap: (){
Navigator.of(context).push(MyPopupRoute());
}
通过自定义路由,我们能封装出各种想要的悬浮框或对话框控件,大大提高了开发的灵活度。
本篇博客视频内容可访问B站链接 路由源码剖析及自定义 您觉得有帮助,别忘了点赞哦
如需要获取完整的Flutter全栈式开发课程,请访问以下地址
Flutter全栈式开发之Dart 编程指南
Flutter 全栈式开发指南