引言
在Flutter中,路由間的頁面跳轉使用的是Navigator.push
、Navigator.pop
方法。
在頁面跳轉時如何將資料傳遞過去,目前有兩種方法:
1、目標頁面的建構函式顯式接收引數。例如跳轉過去的是SearchPage
,接收一個字串引數,則如下所示。
Navigator.push(context, new MaterialPageRoute(builder: (BuildContext context) => new SearchPage("傳遞的引數")));
複製程式碼
2、我們還可以使用Route
的RouteSetting
引數進行資料的傳遞。例如
Navigator.push(context, new MaterialPageRoute(builder: (BuildContext context) => new SearchPage()
, settings: RouteSettings(arguments: "傳遞的引數")));
複製程式碼
在頁面SearchPage
可以這樣獲取傳遞過來的引數:
String arg = ModalRoute.of(context).settings.arguments;
複製程式碼
這裡我們想說一下第二種方式資料傳遞的原理,讓我們從原始碼中一窺究竟吧!
原始碼解析
大推論
究竟怎麼開始分析原始碼呢?我們用打斷點的形式,就能一步一步地看到每一個方法的執行過程,既然最終的目標頁是SearchPage
,那麼我們就將斷點打在SearchPage
的build
方法,由於SearchPage
是一個StatefulWidget
,則斷點打在了SearchPageState
的build
方法裡,如下圖所示。
context
是一個StatefulElement
,依次點開它的_parent
變數,如下兩圖所示。
最終看到了Navigator
的身影。也就是說,使用Navigator.push
方法跳轉到SearchPage
,則相當於將SearchPage
掛載在了Navigator
這個Widget
下,SearchPage
相當於Navigator
的一個子元件。
接下來,我們將斷點打在SearchPage中使用ModalRoute.of
方法這一行,如下圖所示。
ModalRoute
的of
方法。
static ModalRoute<T> of<T extends Object>(BuildContext context) {
final _ModalScopeStatus widget = context.inheritFromWidgetOfExactType(_ModalScopeStatus);
return widget?.route;
}
複製程式碼
這裡的context
就是SearchPageElement
,呼叫了inheritFromWidgetOfExactType
方法,並將_ModalScopeStatus
作為引數傳遞過去。
@override
InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
if (ancestor != null) {
assert(ancestor is InheritedElement);
return inheritFromElement(ancestor, aspect: aspect);
}
_hadUnsatisfiedDependencies = true;
return null;
}
複製程式碼
上面的方法邏輯也挺簡單的,_inheritedWidgets
是一個Map
,如果不為空則獲取_inheritedWidgets
的key
為_ModalScopeStatus
的InheritedElement
物件。那麼_inheritedWidgets
是什麼時候被賦值的呢?我們全域性搜尋一下,發現在 _updateInheritance
中有對_inheritedWidgets
進行操作。
void _updateInheritance() {
assert(_active);
final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
else
_inheritedWidgets = HashMap<Type, InheritedElement>();
_inheritedWidgets[widget.runtimeType] = this;
}
複製程式碼
首先它會將父類(_parent
)的_inheritedWidgets
賦值給自己的_inheritedWidgets
,然後對於_inheritedWidgets
的當前執行型別,將自己(this
)進行賦值。
現在讓我們回過頭來看一下上面的ModalRoute.of
方法。
static ModalRoute<T> of<T extends Object>(BuildContext context) {
final _ModalScopeStatus widget = context.inheritFromWidgetOfExactType(_ModalScopeStatus);
return widget?.route;
}
複製程式碼
第一行返回了_ModalScopeStatus
物件。根據上面的結論,我們知道_inheritedWidgets[_ModalScopeStatus]
就是在_ModalScopeStatus
的_updateInheritance
進行賦值的。
也就是說context.inheritFromWidgetOfExactType
其實是從下往上尋找_ModalScopeStatus
物件,然後of
方法會返回_ModalScopeStatus
的route
變數。
從Navigator
到SearchPage
的Widget
樹可以看到,_ModalScopeStatus
的確存在於上述的Widget
樹中,如下圖所示。
於是我們有理由得出如下大推論:
Navigator.push方法自上而下地產生了從Navigator到_ModalScopeStatus到SearchPage的Widget樹,並且Navigator將路由資訊儲存在了_ModalScopeStatus這個Widget中,SearchPage自下而上尋找_ModalScopeStatus並獲取其中的路由資訊。
驗證大推論
第一個小推論
為了驗證上述推論的正確性,我們從Navigator
的build
方法出發,一步步地看是否真的有將Route
物件傳遞到_ModalScopeStatus
物件。
Widget build(BuildContext context) {
return Listener(
onPointerDown: _handlePointerDown,
onPointerUp: _handlePointerUpOrCancel,
onPointerCancel: _handlePointerUpOrCancel,
child: AbsorbPointer(
absorbing: false, // it's mutated directly by _cancelActivePointers above
child: FocusScope(
node: focusScopeNode,
autofocus: true,
child: Overlay(
key: _overlayKey,
initialEntries: _initialOverlayEntries,
),
),
),
);
}
複製程式碼
從上面的build
方法與上面打斷點時的Widget
樹得知,Navigator
的子Widget
樹為Listener->AbsorbPointer->FocusScope->Overlay
,而Overlay
的build
方法如下。
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,
);
}
複製程式碼
則Overlay
的子Widget
樹為_Theatre->Stack
,而Stack
的children
為onstageChildren
列表,則Stack
的子Widget
為_OverlayEntry
,而_OverlayEntry
的build
方法如下。
Widget build(BuildContext context) {
return widget.entry.builder(context);
}
複製程式碼
結合打斷點後Navigator
的子Widget
樹得知,_OverlayEntry
的子Widget
為_ModalScope
,也就是說我們得出第一個小推論:
widget.entry.builder(context)返回的Widget就是_ModalScope。
驗證第一個小推論
我們驗證一下對不對,widget.entry
其實是從OverlayState
的_enties
列表中獲取的,而_enties
列表是在Navigator
的build
方法中通過Overlay
傳遞_initialOverlayEntries
引數初始化的。
child: Overlay(
key: _overlayKey,
initialEntries: _initialOverlayEntries,
),
複製程式碼
而_initialOverlayEntries
是在Navigator
的initState
初始化的。
void initState() {
...省略
for (Route<dynamic> route in _history)
_initialOverlayEntries.addAll(route.overlayEntries);
}
複製程式碼
這裡傳遞進來的是Route
物件的overlayEntries
,而Route
物件的overlayEntries
,而這裡的Route
物件其實是MaterialPageRoute
,所以的overlayEntries
在MaterialPageRoute
的父類OverlayRoute
被重寫了,所以它真實的賦值是在OverlayRoute
的install
方法裡面,而install
方法在Navigator.push
方法中就被呼叫了,所以我們看一下install
方法。
void install(OverlayEntry insertionPoint) {
assert(_overlayEntries.isEmpty);
_overlayEntries.addAll(createOverlayEntries());
navigator.overlay?.insertAll(_overlayEntries, above: insertionPoint);
super.install(insertionPoint);
}
複製程式碼
_overlayEntries
新增了createOverlayEntries
方法的執行結果。
Iterable<OverlayEntry> createOverlayEntries() sync* {
yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier);
yield OverlayEntry(builder: _buildModalScope, maintainState: maintainState);
}
複製程式碼
上面方法構建了一個OverlayEntry
物件,其中的builder
引數傳了_buildModalScope
,而_buildModalScope
返回的是_ModalScope
物件。
Widget _buildModalScope(BuildContext context) {
return _modalScopeCache ??= _ModalScope<T>(
key: _scopeKey,
route: this,
// _ModalScope calls buildTransitions() and buildChild(), defined above
);
}
複製程式碼
到這裡,我們終於驗證了第一個小推論的正確性,即_OverlayEntry
的子Widget
就是_ModalScope
,而且這個_ModalScope
還包含了路由資訊,就是其中的route
變數。
由小推論驗證大推論的正確性
我們再來看_ModalScope
的build
方法。
Widget build(BuildContext context) {
return _ModalScopeStatus(
route: widget.route,
...省略
);
}
複製程式碼
它返回了_ModalScopeStatus
,並將它的route
變數賦值給了_ModalScopeStatus
的route
變數,我們上面說過_ModalScope
的route
變數就是MaterialPageRoute
物件。
也就是說只要拿到了_ModalScopeStatus
物件,就能通過它的route
方法獲取MaterialPageRoute
物件,並通過其中的setting
變數獲取RouteSetting
物件,而RouteSetting
物件的arguments
變數就是路由傳遞進來的引數了。
看到這裡,我們就不難理解為什麼在SearchPage
中使用下面這樣的方法能獲取路由傳遞進來的資料的原因了。
String arg = ModalRoute.of(context).settings.arguments;
複製程式碼
因為通過ModalRoute.of(context)
,SearchPage
自下而上地尋找_ModalScopeStatus
物件,_ModalScopeStatus
物件找到後通過route
變數找到Route
物件,也就是MaterialPageRoute
物件,然後通過訪問MaterialPageRoute
物件的setting.arguments
就能拿到Navigator
傳遞進來的引數了,這就是Navigator
資料傳遞的原理。
從上述分析中,我們知道了Flutter中一個重要的功能型元件,它就是InheritedWidget,通過它我們可以實現跨元件的資料共享。有關更多InheritedWidget的使用方法,可以參考 資料共享(InheritedWidget)