前言
這是部落格《Flutter路由 - Navigator 》的番外篇,如果你沒有看過主篇真的不建議你直接看這篇文章,因為它真的炒雞炒雞枯燥乏味……
不講客套話啦,我們分別從Navigator
的push
和pop
兩個方法去探索原始碼以及一些重要的細節。
push
當我們想要push一個Page在介面上時,我們可以呼叫如下程式碼:
Navigator.push(
context,
PageRouteBuilder(pageBuilder: (context, animation, secondaryAnimation) {
return MyPage(args);
}));
複製程式碼
我們以Navigator.push(BuildContext context, Route<T> route)
方法為起始進行追述:
static Future<T> push<T extends Object>(BuildContext context, Route<T> route) {
return Navigator.of(context).push(route);
}
複製程式碼
1.NavigatorState
物件檢索
就像上篇文章有提到過的,Navigator.push
是一個靜態方法,使得你可以在任何地方進行呼叫,其內部通過of
方法在Element
樹(BuildContext
是 Element
的抽象類)中進行向上搜尋。我們看下Navigator.of
方法:
static NavigatorState of(
BuildContext context, {
bool rootNavigator = false,
bool nullOk = false,
}) {
final NavigatorState navigator = rootNavigator
? context.rootAncestorStateOfType(const TypeMatcher<NavigatorState>())
: context.ancestorStateOfType(const TypeMatcher<NavigatorState>());
return navigator;
}
複製程式碼
主要通過rootNavigator
變數判斷是否要檢索根部Navigator,rootAncestorStateOfType
向上查詢最根部匹配型別物件,ancestorStateOfType
向上查詢最近的匹配型別物件。從這個方法我們也可以知道Navigator.of
方法查詢的不是Navigator
而是NavigatorState
,這個也比較容易理解,Navigator
是一個StatefulWidget
,具體的邏輯都在它的State物件當中。
2.push實現
接下來看NavigatorState.push
實現:
//0
final List<Route<dynamic>> _history = <Route<dynamic>>[];
Future<T> push<T extends Object>(Route<T> route) {
...
//1
final Route<dynamic> oldRoute = _history.isNotEmpty ? _history.last : null;
//2
route._navigator = this;
//3
route.install(_currentOverlayEntry);
//4
_history.add(route);
//5
route.didPush();
route.didChangeNext(null);
if (oldRoute != null) {
oldRoute.didChangeNext(route);
route.didChangePrevious(oldRoute);
}
//6
for (NavigatorObserver observer in widget.observers)
observer.didPush(route, oldRoute);
_afterNavigation();
return route.popped;
}
複製程式碼
-
0.
_history
就是Navigator
所維護的介面棧,但它只是一個普通的List
。 -
- 獲取原本在棧頂的route,因為
_history
是一個普通的List
,所以棧頂就是最後一個元素。
- 獲取原本在棧頂的route,因為
-
2.讓新加入的
route
和Navigator
引用繫結。 -
3.
install
是route
轉換為OverlayEntry
,並插入到List<OverlayEntry>
中的重要過程,_currentOverlayEntry
是oldRoute
對應的OverlayEntry
,傳入_currentOverlayEntry
的意思是插入到它的上面。具體細節我們稍後細講。 -
4.
route
入棧。 -
5.完成新老介面的轉換,內部有一些事件和動畫處理。
-
6.通知所有的
Navigator
觀察者。
3. route.install
我們最關心的是第3步,route.install(_currentOverlayEntry);
Route
類裡這個方法是一個空實現,具體細節在它的子類中,我們重點看OverlayRoute
的實現:
abstract class OverlayRoute<T> extends Route<T> {
...
@override
List<OverlayEntry> get overlayEntries => _overlayEntries;
final List<OverlayEntry> _overlayEntries = <OverlayEntry>[];
@override
void install(OverlayEntry insertionPoint) {
_overlayEntries.addAll(createOverlayEntries());
navigator.overlay?.insertAll(_overlayEntries, above: insertionPoint);
super.install(insertionPoint);
}
...
複製程式碼
一個Route
的_overlayEntries
通常包含兩個OverlayEntry
,一個是遮罩,一個是介面本身,都在createOverlayEntries
中建立。
先對_overlayEntries
完成兩個OverlayEntry
的add
,然後呼叫navigator
所持有的overlay
物件,將遮罩和介面插入到overlay
所持有的List<OverlayEntry>
中,以備繪製到介面之上。
4. createOverlayEntries
這是Route
到OverlayEntry
的關鍵,具體的實現在ModalRoute
中:
abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T> {
...
@override
Iterable<OverlayEntry> createOverlayEntries() sync* {
yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier);
yield OverlayEntry(builder: _buildModalScope, maintainState: maintainState);
}
...
複製程式碼
對yield
語法不熟悉沒關係,你只要知道這個方法最終會返回兩個OverlayEntry
物件就好了。_modalBarrier
就是遮罩層,就是Dialog那個背景遮罩。
我們重點關心第二個page的建立。OverlayEntry
中傳入兩個引數:
-
builder:這是一個function, 我們自定義的Page建立就在這個方法之中。
-
maintainState: 這個屬性我們在上篇部落格有講,表示當這個 Widget 不可見時,是否需要繼續保持它的狀態,是否需要讓它繼續活著。通常一個 Page
maintainState
為true, Dialog 為 false。
5. _buildModalScope
看下_buildModalScope
實現:
Widget _buildModalScope(BuildContext context) {
return _modalScopeCache ??= _ModalScope<T>(
key: _scopeKey,
route: this,
// _ModalScope calls buildTransitions() and buildChild(), defined above
);
}
複製程式碼
_buildModalScope
建立了一個 Widget _ModalScope
,並將Route
自己傳了進去。_ModalScope
是個啥呢?
_ModalScope
是一個StatefulWidget
,我們直接看它的 State
的build
方法:
@override
Widget build(BuildContext context) {
return _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: FocusScope(
node: widget.route.focusScopeNode, // immutable
child: RepaintBoundary(
child: AnimatedBuilder(
animation: _listenable, // immutable
builder: (BuildContext context, Widget child) {
return widget.route.buildTransitions(
context,
widget.route.animation,
widget.route.secondaryAnimation,
IgnorePointer(
ignoring: widget.route.animation?.status == AnimationStatus.reverse,
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,
);
...
複製程式碼
可以看到_ModalScope
內部又巢狀了很多Widget
,並且建立所用的資料都來自Route
,我們重點關注最後一個Builder
,可以看到它最後return
呼叫的是 route
的buildPage
方法,眼熟嗎?就是我們最開始呼叫push
方法傳入的自定義PageRouteBuilder
物件:
Navigator.push(
context,
PageRouteBuilder(pageBuilder: (context, animation, secondaryAnimation) {
return MyPage(args);
}));
複製程式碼
到這裡你知道你的傳入的 Route 是如何被使用的了。
我們回頭看一下Route
轉化為OverlayEntry
之後,Overlay
是如何處理的:
6. 插入到Overlay中
之前我們是這樣插入到overlay
中的:navigator.overlay?.insertAll(_overlayEntries, above: insertionPoint);
navigator.overlay
實際是OverlayState
class OverlayState extends State<Overlay> with TickerProviderStateMixin {
...
void insertAll(Iterable<OverlayEntry> entries, { OverlayEntry above }) {
...
//1
for (OverlayEntry entry in entries) {
entry._overlay = this;
}
//2
setState(() {
final int index = above == null ? _entries.length : _entries.indexOf(above) + 1;
_entries.insertAll(index, entries);
});
}
...
複製程式碼
-
- 將每一個要插入到集合中的
OverlayEntry
繫結自身,繫結自身的原因是我們上篇文章所講的:元素自治。 插入是由Overlay
中進行的,但刪除卻是每個元素自己呼叫的。
- 將每一個要插入到集合中的
-
- 看到了熟悉的
setState((){}
方法,集合插入完成後,將觸發Overlay
的 rebuild。
- 看到了熟悉的
所以接下來我們看一下 OverlayState
的 build
方法:
@override
Widget build(BuildContext context) {
//1
final List<Widget> onstageChildren = <Widget>[];
final List<Widget> offstageChildren = <Widget>[];
bool onstage = true;
//2
for (int i = _entries.length - 1; i >= 0; i -= 1) {
final OverlayEntry entry = _entries[i];
//3
if (onstage) {
//4
onstageChildren.add(_OverlayEntry(entry));
if (entry.opaque)
onstage = false;
//5
} else if (entry.maintainState) {
offstageChildren.add(TickerMode(enabled: false, child: _OverlayEntry(entry)));
}
}
//6
return _Theatre(
//7
onstage: Stack(
fit: StackFit.expand,
children: onstageChildren.reversed.toList(growable: false),
),
//8
offstage: offstageChildren,
);
}
複製程式碼
-
1.建立兩個空列表,分別儲存“臺上”將要被繪製的,和“臺下”不需要繪製的 Widget。
-
2.開始遍歷所有的OverlayEntry,準備將他們分配到兩個集合當中。值得注意的是,這裡是倒序遍歷,後加入的元素繪製到最上層。
-
3.最開始每個
OverlayEntry
都是有機會被繪製的,直到某個OverlayEntry
的opaque=true
,其他OverlayEntry
沒有機會“上臺“了 -
4.可以看到
OverlayEntry
被當做引數傳給了_OverlayEntry
,完成了一個純 Dart 類到 Widget 的轉換。_OverlayEntry
程式碼很簡單,它將根據OverlayEntry中的屬性
進行 build。 -
5.沒有機會上臺的
OverlayEntry
開始判斷maintainState
值,需要儲存的狀態的進入offstageChildren
,不需要的儲存狀態的,沒有機會參與這一次的 build ,他們將被銷燬。 -
6.分配結束之後,進入劇場:
_Theatre
。 -
7.臺上需要被繪製的進入
Stack
元件,準備繪製。 -
8.不需要被繪製的,只會進行build。
至此,新push的Page完成了建立和繪製。
pop
看完了 push , 我們在看一下 pop:
1.pop
bool pop<T extends Object>([ T result ]) {
...
//1
final Route<dynamic> route = _history.last;
bool debugPredictedWouldPop;
//2
if (route.didPop(result ?? route.currentResult)) {
if (_history.length > 1) {
//3
_history.removeLast();
if (route._navigator != null)
_poppedRoutes.add(route);
//4
_history.last.didPopNext(route);
//5
for (NavigatorObserver observer in widget.observers)
observer.didPop(route, _history.last);
} else {
return false;
}
} else {
...
}
_afterNavigation();
return true;
}
複製程式碼
-
1.獲取集合末尾,也就是棧頂的
route
,它將被pop。 -
2.可以看到
didPop
是有返回值的,也就說如果返回了 false,是可以不彈出的。如果返回了 true,didPop
內部有一些銷燬處理,我們稍後看。 -
3.如果
didPop
返回了 true, 會做出棧處理。 -
4.通知下一個route 你回到前臺了。
-
5.通知所有的觀察者。
2.didPop
我們重點關注 Page 回收的處理,所以看一下OverlayRoute
中的 didPop
:
abstract class OverlayRoute<T> extends Route<T> {
...
@override
bool didPop(T result) {
final bool returnValue = super.didPop(result);
if (finishedWhenPopped)
navigator.finalizeRoute(this);
return returnValue;
}
複製程式碼
看一下navigator.finalizeRoute
void finalizeRoute(Route<dynamic> route) {
...
route.dispose();
}
複製程式碼
↓
abstract class OverlayRoute<T> extends Route<T> {
...
@override
void dispose() {
for (OverlayEntry entry in _overlayEntries)
entry.remove();
_overlayEntries.clear();
super.dispose();
}
複製程式碼
可以看到遍歷執行了所有OverlayEntry
的remove
方法:
void remove() {
//1
final OverlayState overlay = _overlay;
_overlay = null;
//2
if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) {
SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
//3
overlay._remove(this);
});
} else {
overlay._remove(this);
}
}
複製程式碼
-
1.清楚引用,避免記憶體洩露
-
2.判斷一下當前排程機狀態,稍後或立刻執行
OverlayState
的_remove
方法.
void _remove(OverlayEntry entry) {
if (mounted) {
_entries.remove(entry);
setState(() { /* entry was removed */ });
}
}
複製程式碼
從集合中清楚當前的OverlayEntry
,並觸發一次 Overlay
的rebuild,因為_entries
已經沒有當前介面了,rebuild之後也就自然不會存在了。
結語
好啦,push和pop的程式碼流程都過完啦~