在Flutter
中,頁面的跳轉是經過Navigator
來實現。經過幾句簡單的代碼就能夠實現頁面的跳轉並傳遞對應的參數。那麼具體實現是怎樣的尼?下面就來一窺究竟。數組
在Flutter
中,一切皆Widget
,Navigator
也不能例外。但咱們並無主動添加Navigator
,可是又能夠經過Navigator
來進行頁面跳轉,這是怎麼回事尼?緩存
咱們在進行Flutter
開發時,必須以MaterialApp
或WidgetsApp
來做爲第一個Widget
,不然就會出錯。也就是在WidgetsApp
中(MaterialApp
是對WidgetsApp
的包裝),Flutter
默認給咱們添加了第一個Navigator
,通常跳轉都是根據這個Navigator
來進行的。這也就是咱們沒有主動添加Navigator
,但卻可以經過Navigator
來進行頁面跳轉的緣由。以下圖所示。 markdown
Widget
樹中的第一個
Navigator
,即根
Navigator
。
對於Navigator
,咱們通常都是經過Navigator.of(context)
來獲取NavigatorState
對象,而後經過NavigatorState
對象中的函數來實現頁面跳轉、關閉、替換等。下面來看Navigator.of(context)
的實現。ide
static NavigatorState of(
BuildContext context, {
bool rootNavigator = false,
bool nullOk = false,
}) {
final NavigatorState navigator = rootNavigator
//獲取根Navigator
? context.findRootAncestorStateOfType<NavigatorState>()
//獲取距離當前Widget最近的Navigator
: context.findAncestorStateOfType<NavigatorState>();
return navigator;
}
複製代碼
rootNavigator
爲true
表示獲取根Navigator
,不然獲取距離當前Widget
最近的Navigator
,默認爲false
。函數
能夠發現,這裏要想獲取根Navigator
,就須要傳遞一個context
對象,但在某些需求(如token
失效跳轉登陸頁)下,沒法拿到context
對象,但又須要跳轉,那這該怎麼辦尼?post
這時候咱們能夠給根Navigator
傳遞一個key
,而後根據這個key
拿到NavigatorState
對象。而MaterialApp
又給咱們提供了向根Navigator
傳遞key
的機會。動畫
class MaterialApp extends StatefulWidget {
const MaterialApp({
Key key,
//自定義key
this.navigatorKey,
...
})
}
複製代碼
經過給navigatorKey
賦一個GlobalKey
對象,而後經過這個對象就能夠不須要context
來得到根Navigator
,從而進行頁面的跳轉、返回、替換等。ui
當咱們正確的拿到NavigatorState
對象後,就能夠經過push
函數來跳轉到到新的頁面。this
@optionalTypeArgs
Future<T> push<T extends Object>(Route<T> route) {
//獲取上一個route
final Route<dynamic> oldRoute = _history.isNotEmpty ? _history.last : null;
route._navigator = this;
//獲取當前OverlayEntry對象
route.install(_currentOverlayEntry);
//將當前route添加到集合中
_history.add(route);
route.didPush();
route.didChangeNext(null);
if (oldRoute != null) {
oldRoute.didChangeNext(route);
route.didChangePrevious(oldRoute);
}
for (NavigatorObserver observer in widget.observers)
observer.didPush(route, oldRoute);
RouteNotificationMessages.maybeNotifyRouteChange(_routePushedMethod, route, oldRoute);
_afterNavigation(route);
return route.popped;
}
複製代碼
在這裏,route
(路由)是一個很是重要的概念,它對頁面進行了抽象。在Flutter
中,一個頁面對應一個route
對象。_history
是NavigatorState
中的一個數組,也是一般所說的路由棧。在跳轉一個新頁面時,會將route
對象添加到_history
數組中,彈出頁面則從該數組中移除route
對象。spa
在上面代碼中,route.install(_currentOverlayEntry)
是頁面跳轉的核心實現。install
函數在Route
中是一個空實現,在其子類ModalRoute
中進行了具體實現。
abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T> {
......
@override
void install(OverlayEntry insertionPoint) {
super.install(insertionPoint);
_animationProxy = ProxyAnimation(super.animation);
_secondaryAnimationProxy = ProxyAnimation(super.secondaryAnimation);
}
......
}
複製代碼
在ModalRoute
裏的install
函數中主要作了動畫相關的處理,而後交給父類執行。在其父類TransitionRoute
主要是作動畫的建立及相關處理,並交給其父類處理。那麼再來看TransitionRoute
的父類OverlayRoute
。
abstract class OverlayRoute<T> extends Route<T> {
.....
/// Subclasses should override this getter to return the builders for the overlay.
Iterable<OverlayEntry> createOverlayEntries();
/// The entries this route has placed in the overlay.
@override
List<OverlayEntry> get overlayEntries => _overlayEntries;
final List<OverlayEntry> _overlayEntries = <OverlayEntry>[];
@override
void install(OverlayEntry insertionPoint) {
//建立頁面對應的兩個OverlayEntry對象並加入到_overlayEntries中,以待後續處理。
_overlayEntries.addAll(createOverlayEntries());
//進行UI的更新
navigator.overlay?.insertAll(_overlayEntries, above: insertionPoint);
//空實現
super.install(insertionPoint);
}
......
}
複製代碼
在OverlayRoute
中有一個抽象函數createOverlayEntries
,該函數須要在子類實現。代碼以下。
@override
Iterable<OverlayEntry> createOverlayEntries() sync* {
yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier);
yield OverlayEntry(builder: _buildModalScope, maintainState: maintainState);
}
複製代碼
yield
的含義先無論。該函數主要是建立兩個OverlayEntry
對象,一個是咱們須要展現頁面所對應的OverlayEntry
對象,一個是Barrier所對應的OverlayEntry
對象。它兩的關係以下。
route
所對應的兩個
OverlayEntry
對象建立成功後,就加入到集合
_overlayEntries
中,而後調用
OverlayState
的
insertAll
函數。
class OverlayState extends State<Overlay> with TickerProviderStateMixin {
final List<OverlayEntry> _entries = <OverlayEntry>[];
......
void insertAll(Iterable<OverlayEntry> entries, { OverlayEntry below, OverlayEntry above }) {
if (entries.isEmpty)
return;
for (OverlayEntry entry in entries) {
entry._overlay = this;
}
//更新UI
setState(() {
_entries.insertAll(_insertionIndex(below, above), entries);
});
}
@override
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,
);
}
......
}
複製代碼
在insertAll
裏,就能夠發現頁面跳轉的本質了。就是經過setState
來更新UI。
再來看OverlayState
的build
的函數,在該函數中會對須要保存在內存中的頁面進行分類,主要有如下兩類。
maintainState
爲true時就會添加到該集合,目前面跳轉的maintainState
參數默認爲true。來看一個示例。假設有三個頁面page一、page2及page3。咱們先給page2頁面的maintainState
屬性設置不一樣值,而後觀察page2跳轉到page3時page2頁面的變化狀況。
能夠發現,當maintainState
爲true時,會在內存中緩存page2頁面的全部對象,以便下次操做。固然若是後面不須要再次展現page2,那麼就最好將maintainState
設爲false。
前面分析了在flutter
中如何跳轉一個新的頁面,那麼來看一下如何關閉一個頁面。關閉頁面是調用的pop
函數。
bool pop<T extends Object>([ T result ]) {
//從路由棧中獲取當前頁面對應的route對象
final Route<dynamic> route = _history.last;
bool debugPredictedWouldPop;
if (route.didPop(result ?? route.currentResult)) {
if (_history.length > 1) {
//從集合中移除當前頁面所對應的route對象
_history.removeLast();
// If route._navigator is null, the route called finalizeRoute from
// didPop, which means the route has already been disposed and doesn't
// need to be added to _poppedRoutes for later disposal.
if (route._navigator != null)
_poppedRoutes.add(route);
_history.last.didPopNext(route);
for (NavigatorObserver observer in widget.observers)
observer.didPop(route, _history.last);
RouteNotificationMessages.maybeNotifyRouteChange(_routePoppedMethod, route, _history.last);
} else {
return false;
}
} else {}
_afterNavigation<dynamic>(route);
return true;
}
複製代碼
這裏重點是didPop
這個函數。該函數中會調用NavigatorState
的finalizeRoute
函數來釋放資源。而finalizeRoute
又會調用route
的dispose
函數。
abstract class OverlayRoute<T> extends Route<T> {
@override
bool didPop(T result) {
final bool returnValue = super.didPop(result);
assert(returnValue);
//釋放資源操做
if (finishedWhenPopped)
navigator.finalizeRoute(this);
return returnValue;
}
//釋放資源
@override
void dispose() {
//從_overlayEntries集合中移除當前頁面及Barrier層
for (OverlayEntry entry in _overlayEntries)
entry.remove();
_overlayEntries.clear();
super.dispose();
}
}
複製代碼
在dispose
函數中,會將_overlayEntries
集合中清空,這裏之因此是集合並遍歷,是由於一個頁面對應2個OverlayEntry
對象。
void remove() {
final OverlayState overlay = _overlay;
//取消OverlayEntry對OverlayState對象的引用
_overlay = null;
if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) {
//若是當前正在渲染UI,則會等待渲染完畢後,才更新。
SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
//更新UI
overlay._remove(this);
});
} else {
//更新UI
overlay._remove(this);
}
}
複製代碼
再來看OverlayState
的_remove
函數,該函數也是調用了setState
函數。而後等待UI的刷新便可。
class OverlayState extends State<Overlay> with TickerProviderStateMixin {
final List<OverlayEntry> _entries = <OverlayEntry>[];
......
void _remove(OverlayEntry entry) {
if (mounted) {
setState(() {
_entries.remove(entry);
});
}
}
......
}
複製代碼
再來看上一個示例,在page2頁面關閉時的變化狀況。
能夠發現,當maintainState
爲false時,從page3返回page2,會從新建立page2頁面的全部對象。若是page2頁面比較複雜,那麼此舉就比較耗資源,想必這也是maintainState
默認爲true的緣由吧。
固然,Navigator
還有其餘實現函數,如pushNamed
(根據routeName跳轉)、pushAndRemoveUntil
(關閉並跳轉到新頁面)、replace
(替換當前頁面)等。但這些函數的具體實現跟push
及pop
的實現基本上一致,因此只要理解了flutter
中如何進行頁面跳轉,那麼也就基本上了解了Navigator
的的實現原理。也就知道了彈窗的打開、關閉的實現原理,由於這些操做也是經過Navigator
來實現的。
【參考資料】