Navigator 2.0作為新一代的路由提供了申明式的API蜓席,更加符合Flutter的風(fēng)格。Navigator 2.0向前兼容仔戈,新增了一些新的API推姻,使用的方式和Navigator 1.0相比有較大的差別。
本文將詳細解析Navigator 2.0的底層邏輯课兄,讓大家對它有一個深入的了解滓鸠,這樣在使用上會更加的得心應(yīng)手。
Navigator 2.0 誕生的背景
Flutter官方團隊改造路由主要有幾點原因:
-
Navigator 1.0 只提供了一些
push()
,pushNamed()
和pop()
等簡單的API第喳。實現(xiàn)壓入或者彈出多個頁面很困難糜俗,更難實現(xiàn)對棧內(nèi)中間頁面的移除,交換等操作曲饱; - Flutter隨著2.0的到來實現(xiàn)了全平臺的支持悠抹,這樣也就新出現(xiàn)一些使用場景,譬如網(wǎng)頁修改URL地址等扩淀,這些就需要新的API來支持楔敌;
- Navigator 2.0滿足了嵌套路由的需求場景,這樣開發(fā)者在使用時就更加的靈活和方便驻谆;
- Navigator 2.0提供的是申明式的API卵凑,解決了以前路由命令式編程的方式,讓編程的風(fēng)格統(tǒng)一胜臊。
Navigator 2.0的API雖然比較的多勺卢,但是邏輯還是比較清晰的,我們來一個個的進行介紹象对。
Page
Page代表頁面不可變的的配置信息黑忱,代表一個頁面,類似于Widget配置信息轉(zhuǎn)換成Element, Page配置的信息會轉(zhuǎn)換成Route勒魔。
abstract class Page<T> extends RouteSettings {
const Page({
this.key,
String? name,
Object? arguments,
this.restorationId,
}) : super(name: name, arguments: arguments);
bool canUpdate(Page<dynamic> other) {
return other.runtimeType == runtimeType &&
other.key == key;
}
@factory
Route<T> createRoute(BuildContext context);
}
createRoute
就是轉(zhuǎn)換成Route的方法甫煞;canUpdate
的實現(xiàn)方式和Widget的一樣,也是用于diff
算法冠绢。
RouteSettings
Page的父類RouteSettings僅僅用來保存name
和arguments
這兩個值抚吠。
const RouteSettings({
this.name,
this.arguments,
});
Route
Route代表一個頁面,是Navigator棧中真正管理的內(nèi)容弟胀。
abstract class Route<T> {
// 1
RouteSettings get settings => _settings;
NavigatorState? get navigator => _navigator;
// 2
List<OverlayEntry> get overlayEntries => const <OverlayEntry>[];
// 3
void install() {}
TickerFuture didPush() {}
...
}
- Route持有了配置對象
page
和管理它的navigator
對象;- Route還持有一個OverlayEntry數(shù)組楷力,OverlayEntry放置在類似于Stack的Overlay上蕊玷,我們寫的頁面就是放置在一個OverlayEntry上的;
- Route還定義了一些協(xié)議方法需要子類覆寫弥雹,這些方法主要是route的狀態(tài)變化后收到的回調(diào)函數(shù),這些函數(shù)調(diào)用主要來自于
_RouteEntry
延届。
方法 | 調(diào)用時機 |
---|---|
install |
被插入navigator
|
didPush |
動畫進入顯示 |
didAdd |
直接顯示 |
didReplace |
替換舊的route |
didPop |
請求pop頁面 |
didComplete |
pop完成后 |
didPopNext |
當(dāng)前route后面的route被pop |
didChangeNext |
當(dāng)前route后面的route被替換 |
didChangePrevious |
當(dāng)前route前面的route被替換 |
changedInternalState |
當(dāng)前route的state變化后 |
changedExternalState |
當(dāng)前route的navigator 變化后 |
MaterialPage
和 _PageBasedMaterialPageRoute
我們可以直接使用系統(tǒng)給我們提供的Page類剪勿,也可以自定義繼承自Page的類。我們來看看官方給我們提供的MaterialPage的邏輯方庭。
MaterialPage的Route是_PageBasedMaterialPageRoute
類厕吉,它的繼承邏輯是:_PageBasedMaterialPageRoute
-> PageRoute
-> ModalRoute
-> TransitionRoute
-> OverlayRoute
+ LocalHistoryRoute
-> Route
。
LocalHistoryRoute
LocalHistoryRoute可以給Route添加一些LocalHistoryEntry械念。當(dāng)LocalHistoryEntry不為空時头朱,didPop
方法調(diào)用的時候會移除最后一個LocalHistoryEntry,否則Route就要被pop
了龄减。
OverlayRoute
OverlayRoute主要是持有Route對應(yīng)的OverlayEntry
數(shù)組项钮,這個數(shù)組是子類在被插入navigator
的時候?qū)ζ溥M行賦值的。
abstract class OverlayRoute<T> extends Route<T> {
@factory
Iterable<OverlayEntry> createOverlayEntries();
List<OverlayEntry> get overlayEntries => _overlayEntries;
void install() {
_overlayEntries.addAll(createOverlayEntries());
super.install();
}
}
TransitionRoute
TransitionRoute是主要是負(fù)責(zé)動畫部分希停。
abstract class TransitionRoute<T> extends OverlayRoute<T> {
Animation<double>? get animation => _animation;
Animation<double>? get secondaryAnimation => _secondaryAnimation;
void install() {
_animation = createAnimation()
..addStatusListener(_handleStatusChanged);
super.install();
}
TickerFuture didPush() {
super.didPush();
return _controller!.forward();
}
void didAdd() {
super.didAdd();
_controller!.value = _controller!.upperBound;
}
bool didPop(T? result) {
_controller!.reverse();
return super.didPop(result);
}
void didPopNext(Route<dynamic> nextRoute) {
_updateSecondaryAnimation(nextRoute);
super.didPopNext(nextRoute);
}
void didChangeNext(Route<dynamic>? nextRoute) {
_updateSecondaryAnimation(nextRoute);
super.didChangeNext(nextRoute);
}
}
- TransitionRoute有
_animation
和secondaryAnimation
兩個動畫,前者負(fù)責(zé)當(dāng)前Route的push和pop動畫烁巫,后者負(fù)責(zé)下一個Route進行push和pop時本身這個Route的動畫。_animation
是install
就生成了宠能,secondaryAnimation
可以大部分情況下就是下一個Route的_animation
, 所以didPopNext
和didChangeNext
時需要更新secondaryAnimation
亚隙。- 如果不需要動畫時調(diào)用的是
didAdd
方法,Route是被動調(diào)用的這個方法违崇,其實是_RouteEntry
根據(jù)(Navigator確定的)狀態(tài)判斷調(diào)用的這個方法阿弃。
ModalRoute
ModalRoute主要的作用是阻止除最上層的Route之外的Route進行用戶交互,其中的知識點也是非常豐富的羞延。
abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T> {
Iterable<OverlayEntry> createOverlayEntries() sync* {
yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier);
yield _modalScope = OverlayEntry(builder: _buildModalScope, maintainState: maintainState);
}
}
- ModalRoute生成了兩個非常重要的
OverlayEntry
---_modalBarrier
和_modalScope
渣淳。_modalBarrier
實現(xiàn)了阻止用戶對最上層Route之外的Route進行用戶交互的功能;_modalScope
會持有router自身伴箩,_modalScope
在構(gòu)建的時候就會調(diào)用router的buildTransitions
和buildChild
方法水由,參數(shù)都包含router的animation
和secondaryAnimation
,也就是TransitionRoute中的兩個動畫屬性;
Widget _buildModalScope(BuildContext context) {
return _modalScopeCache ??= Semantics(
sortKey: const OrdinalSortKey(0.0),
child: _ModalScope<T>(
key: _scopeKey,
route: this,
// _ModalScope calls buildTransitions() and buildChild(), defined above
)
);
}
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation);
Widget buildTransitions(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return child;
}
我們接下來看看_ModalScope
的_ModalScopeState
的內(nèi)容:
class _ModalScopeState<T> extends State<_ModalScope<T>> {
late Listenable _listenable;
final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: '$_ModalScopeState Focus Scope');
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) {
widget.route.navigator!.focusScopeNode.setFirstFocus(focusScopeNode);
}
}
}
_listenable
是route的animation
和secondaryAnimation
的組合赛蔫;focusScopeNode
是焦點砂客,初始化的時候?qū)?code>navigator的焦點設(shè)置為這個焦點,這樣就實現(xiàn)了最上層的Route才獲取到焦點呵恢,屏蔽對其他Route的焦點獲染现怠;
Widget build(BuildContext context) {
// 1 RestorationScope
return AnimatedBuilder(
animation: widget.route.restorationScopeId,
builder: (BuildContext context, Widget? child) {
return RestorationScope(
restorationId: widget.route.restorationScopeId.value,
child: child!,
);
},
// 2 _ModalScopeStatus
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
// 3 RepaintBoundary
child: RepaintBoundary(
// 4. AnimatedBuilder
child: AnimatedBuilder(
animation: _listenable, // immutable
builder: (BuildContext context, Widget? child) {
// 5. buildTransitions
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!,
);
},
),
),
),
),
),
),
);
},
),
),
),
),
);
}
_ModalScopeState
的build
方法是設(shè)計非常精妙的一個方法:
RestorationScope
負(fù)責(zé)Route用于恢復(fù)數(shù)據(jù)的作用渗钉;_ModalScopeStatus
是InheritedWidget彤恶,它保持對Route的引用钞钙,所以我們在調(diào)用ModalRoute.of(contex)
獲取頁面?zhèn)鲄r,就是通過獲取的這個_ModalScopeStatus
声离,再找到對應(yīng)的傳參芒炼。- 中間放置了一個
RepaintBoundary
可以限制重繪的區(qū)域,這樣可以提高進行動畫時繪制的效率术徊;- 最底層的
AnimatedBuilder
這個Widget是核心本刽,這個AnimatedBuilder
的child
是由route.buildPage()
這個方法創(chuàng)建的,其實就是我們Page的child赠涮,即開發(fā)者寫的頁面內(nèi)容子寓;這個AnimatedBuilder
的builder
方法中調(diào)用了route.buildTransitions()
,它驅(qū)動動畫是_listenable
,也就是說animation
和secondaryAnimation
都能驅(qū)動它的動畫過程笋除。這其實很好理解:當(dāng)前Route的pop和push和下個Route的pop和push都會觸發(fā)動畫的產(chǎn)生斜友。
PageRoute
PageRoute
主要就是讓最上層下面的Route不可見,點擊_modalBarrier
不讓當(dāng)前Route從Navigator棧中彈出垃它。
abstract class PageRoute<T> extends ModalRoute<T> {
@override
bool get opaque => true;
@override
bool get barrierDismissible => false;
}
_PageBasedMaterialPageRoute
_PageBasedMaterialPageRoute
的作用是覆寫了buildPage
方法, 返回的是開發(fā)者寫的界面鲜屏;
class _PageBasedMaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixin<T> {
Widget buildContent(BuildContext context) {
return _page.child;
}
}
官方為我們提供了默認(rèn)的pop和push動畫,它們就在混入的MaterialRouteTransitionMixin
中實現(xiàn)的国拇。MaterialRouteTransitionMixin
會根據(jù)不同的平臺有不同的實現(xiàn)墙歪,iOS是左右的動畫,Android是上下的動畫贝奇,web也是左右動畫虹菲。
我們以iOS為例,其最后使用的是CupertinoPageTransition
這個類的方法:
SlideTransition(
position: _secondaryPositionAnimation,
textDirection: textDirection,
transformHitTests: false,
child: SlideTransition(
position: _primaryPositionAnimation,
textDirection: textDirection,
child: DecoratedBoxTransition(
decoration: _primaryShadowAnimation,
child: child,
),
)
看到SlideTransition嵌套到一個child上是不是很疑惑掉瞳?兩個動畫用在一個Widget上毕源?
先解釋下其他參數(shù):
textDirection
決定了滑動的方法,因為有些語言是從右到左排序的陕习;transformHitTests
設(shè)置為flase,點擊事件的響應(yīng)位置不受動畫的影響霎褐;_primaryShadowAnimation
是設(shè)置了一個動畫中的陰影。
_secondaryPositionAnimation
是從Offset.zero
到Offset(-1.0/3.0, 0.0)
该镣,正常情況下就是從右往左移動1/3的屏幕寬度冻璃。
final Animatable<Offset> _kMiddleLeftTween = Tween<Offset>(
begin: Offset.zero,
end: const Offset(-1.0/3.0, 0.0),
);
_primaryPositionAnimation
是從Offset(1.0, 0.0)
到Offset.zero
,正常情況下就是從不可見的屏幕右邊移動到屏幕最左邊损合,然后占據(jù)整個屏幕寬度省艳。
final Animatable<Offset> _kRightMiddleTween = Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
);
我們接下來解釋下pop一個Route時候的動畫邏輯, Animation:0->1
- 新加的Route是被
_primaryPositionAnimation
直接驅(qū)動的,也就是執(zhí)行了從右到左的_kRightMiddleTween
動畫; -
_secondaryPositionAnimation
只是被修改了值嫁审,我們前面TransitionRoute的介紹中提到過跋炕,新加入Route的animation
賦值給了前一個Route的secondaryAnimation
屬性。_ModalScopeState
中介紹過secondaryAnimation
也能驅(qū)動Route的動畫律适,也就是說前一個Route也能產(chǎn)生一個_kMiddleLeftTween
動畫辐烂;
概括:
新加的Route通過animation
驅(qū)動從屏幕右邊移動到左邊的動畫遏插,animation
賦值給了前一個Route的secondaryAnimation
驅(qū)動前一個Route向左移動1/3個屏幕位置。
push的邏輯類似纠修,只是一個反向的動畫reverse
胳嘲。前一個Route在secondaryAnimation
的驅(qū)動下右移了1/3屏幕寬度,當(dāng)前的Route在animation
驅(qū)動下移出屏幕扣草。
我們可以點擊Flutter DevTools的Slow Animations看看動畫的慢放過程:
階段總結(jié)
_RouteEntry
Navigator不是直接操作的Route了牛,而是Route的封裝類_RouteEntry
。
_RouteEntry(
this.route,
{
required _RouteLifecycle initialState,
this.restorationInformation,
})
_RouteEntry除了持有route
外德召,還持有一個_RouteLifecycle
,即路由狀態(tài)汽纤。
函數(shù)則主要是修改_RouteLifecycle
狀態(tài)的函數(shù)上岗,譬如markForPush
,markForAdd
,markForPop
,markForRemove
,markForComplete
等。此外還有_RouteLifecycle
被標(biāo)記后對Route進行操作函數(shù)蕴坪,譬如handlePush
肴掷,handleAdd
,handlePop
,remove
等。
Navigator
Navigator({
Key? key,
this.pages = const <Page<dynamic>>[],
// ...
})
Navigator
的構(gòu)造方法中有一個關(guān)鍵的屬性pages
,Navigator會將傳入的pages
會轉(zhuǎn)換成Routes對應(yīng)的_RouteEntry
數(shù)組背传。
實現(xiàn)申明式編程的邏輯就是修改這個
pages
中的內(nèi)容呆瞻,Navigator會自動實現(xiàn)對應(yīng)的跳轉(zhuǎn),返回径玖,替換等操作痴脾。Navigator.push
,Navigator.pop
等以前使用的方法就被將不是開發(fā)者需要考慮的使用方法了。
我們接下來分析NavigatorState的重要代碼梳星。
class NavigatorState extends State<Navigator> with TickerProviderStateMixin, RestorationMixin {
List<_RouteEntry> _history = <_RouteEntry>[];
late GlobalKey<OverlayState> _overlayKey;
OverlayState? get overlay => _overlayKey.currentState;
final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: 'Navigator Scope');
}
_history
就是pages
中每個Page通過createRoute
生成的_RouteEntry
數(shù)組赞赖;- OverlayState
overlay
代表的就是OverLay,它負(fù)責(zé)擺放每個Route的overlayEntries
數(shù)組冤灾;OverLay就相當(dāng)于一個Stack前域,專門用于放置OverlayEntry。
NavigatorState的核心方法是didUpdateWidget
方法, 其調(diào)用了一個_updatePages()
方法:
void didUpdateWidget(Navigator oldWidget) {
_updatePages();
}
_updatePages
方法的主要作用是對pages
進行diff比對韵吨,更新_history
數(shù)組中每個_routeEntry
的_RouteLifecycle
, 最后調(diào)用_flushHistoryUpdates()
方法匿垄。
_routeEntry
比對的方法和MultiChildRenderObjectElement
的比對方法是一樣的,先前往后比對能復(fù)用的元素归粉,然后從后往前比對能復(fù)用的元素椿疗,然后對剩下的元素進行復(fù)用或者新建,不能復(fù)用的元素進行銷毀糠悼。
void _flushHistoryUpdates({bool rearrangeOverlay = true}) {
final List<_RouteEntry> toBeDisposed = <_RouteEntry>[];
while (index >= 0) {
switch (entry!.currentState) {
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;
// ...
}
index -= 1;
next = entry;
entry = previous;
previous = index > 0 ? _history[index - 1] : null;
}
_flushObserverNotifications();
_flushRouteAnnouncement();
for (final _RouteEntry entry in toBeDisposed) {
for (final OverlayEntry overlayEntry in entry.route.overlayEntries)
overlayEntry.remove();
entry.dispose();
}
if (rearrangeOverlay) {
overlay?.rearrange(_allRouteOverlayEntries);
}
}
- 根據(jù)每個
_RouteEntry
的_RouteLifecycle
調(diào)用對應(yīng)的方法变丧,例如如果Route被標(biāo)記為_RouteLifecycle.push
,則調(diào)用handlePush
方法,這樣此Route就會調(diào)用install
方法插入Navigator的樹中绢掰,然后進行動畫痒蓬;_flushObserverNotifications
是對每個_NavigatorObservation
監(jiān)聽者進行通知童擎;_flushRouteAnnouncement
主要是對每個Route的前后關(guān)系進行梳理更新,secondaryAnimation
的更新就是這個時候進行的攻晒;- 將不需要的
_RouteEntry
的overlayEntries
從Overlay上移除顾复,因為不需要再顯示了;- 然后將所有的
_RouteEntry
的overlayEntries
更新到Overlay上,代碼在build
方法中可以看到添加的邏輯如下鲁捏。
Widget build(BuildContext context) {
return HeroControllerScope.none(
child: 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: UnmanagedRestorationScope(
bucket: bucket,
child: Overlay(
key: _overlayKey,
initialEntries: overlay == null ? _allRouteOverlayEntries.toList(growable: false) : const <OverlayEntry>[],
),
),
),
),
),
);
}
順便提一下HeroControllerScope是負(fù)責(zé)進行Hero動畫的的Widget芯砸,類似于Android中的共享元素動畫。
階段總結(jié)
到目前為止给梅,我們通過切換Navigator的page
就能夠?qū)崿F(xiàn)路由切換了假丧,是不是文章就結(jié)束了?沒有动羽,因為Navigator 2.0是為Flutter 2.0 的全平臺而生的包帚,目前還沒有解決一些問題,例如編輯瀏覽器網(wǎng)址运吓,網(wǎng)頁返回渴邦,安卓物理鍵返回等功能。
Router
Router({
Key? key,
this.routeInformationProvider,
this.routeInformationParser,
required this.routerDelegate,
this.backButtonDispatcher,
})
final RouteInformationProvider? routeInformationProvider;
final RouteInformationParser<T>? routeInformationParser;
final RouterDelegate<T> routerDelegate;
final BackButtonDispatcher? backButtonDispatcher;
我們看到Router有四個屬性拘哨,RouteInformationProvider
路由信息提供者谋梭,RouteInformationParser
路由信息解析者,RouterDelegate
路由信息的處理代理倦青,BackButtonDispatcher
返回處理的分發(fā)者瓮床。他們四個協(xié)同作用,共同實現(xiàn)路由的功能产镐。
RouteInformation
上面說的到路由信息就是指RouteInformation
纤垂,包括路由的路徑location
和路由對應(yīng)的狀態(tài)state
。這里所指的狀態(tài)就是數(shù)據(jù)磷账。
class RouteInformation {
final String? location;
final Object? state;
}
RouteInformationProvider
RouteInformationProvider
只有一個抽象方法routerReportsNewRouteInformation
峭沦,這個方法的作用是根據(jù)RouteInformation
進行一些額外的操作。
abstract class RouteInformationProvider extends ValueListenable<RouteInformation?> {
void routerReportsNewRouteInformation(RouteInformation routeInformation) {}
}
系統(tǒng)默認(rèn)使用的是PlatformRouteInformationProvider
, 它的routerReportsNewRouteInformation
方法中回調(diào)了系統(tǒng)路由的更新逃糟,例如瀏覽器就會在History棧中新增一條歷史訪問記錄:
class PlatformRouteInformationProvider extends RouteInformationProvider with WidgetsBindingObserver, ChangeNotifier {
void routerReportsNewRouteInformation(RouteInformation routeInformation) {
SystemNavigator.routeInformationUpdated(
location: routeInformation.location!,
state: routeInformation.state,
);
_value = routeInformation;
}
}
RouteInformationParser
這個類的作用是對T頁面模型和RouteInformation路由信息進行相互轉(zhuǎn)換:
abstract class RouteInformationParser<T> {
Future<T> parseRouteInformation(RouteInformation routeInformation);
RouteInformation? restoreRouteInformation(T configuration) => null;
}
parseRouteInformation
這個方法主要是解析初始路由的時候會使用到吼鱼,例如 根據(jù)RouteInformation(location: "/")
顯示啟動頁面;
restoreRouteInformation
這個方法就是根據(jù)T頁面模型生成對應(yīng)的RouteInformation
绰咽。
RouterDelegate
RouterDelegate
顧名思義就是代替Router
工作的類菇肃,它包括根據(jù)T頁面模型添加一個頁面,pop一個頁面取募,提供構(gòu)建的內(nèi)容等琐谤。
abstract class RouterDelegate<T> extends Listenable {
Future<void> setInitialRoutePath(T configuration) {
return setNewRoutePath(configuration);
}
Future<void> setNewRoutePath(T configuration);
Future<bool> popRoute();
T? get currentConfiguration => null;
Widget build(BuildContext context);
}
可以混入
PopNavigatorRouterDelegateMixin
的popRoute
方法,就不用自己去實現(xiàn)了玩敏。
我們從源碼角度看看RouteInformationProvider
斗忌,RouteInformationParser
和RouterDelegate
他們?nèi)咴诔跏蓟酚墒侨绾螌崿F(xiàn)的:
class _RouterState<T> extends State<Router<T>> {
void initState() {
super.initState();
if (widget.routeInformationProvider != null) {
_processInitialRoute();
}
}
void _processInitialRoute() {
_currentRouteInformationParserTransaction = Object();
_currentRouterDelegateTransaction = Object();
_lastSeenLocation = widget.routeInformationProvider!.value!.location;
widget.routeInformationParser!
.parseRouteInformation(widget.routeInformationProvider!.value!)
.then<T>(_verifyRouteInformationParserStillCurrent(_currentRouteInformationParserTransaction, widget))
.then<void>(widget.routerDelegate.setInitialRoutePath)
.then<void>(_verifyRouterDelegatePushStillCurrent(_currentRouterDelegateTransaction, widget))
.then<void>(_rebuild);
}
}
在_processInitialRoute
方法中我們看到了质礼,routeInformationParser
解析routeInformationProvider
的value
,然后routerDelegate
根據(jù)這個解析的結(jié)果去調(diào)用setNewRoutePath
設(shè)置路由织阳。
routeInformationProvider
-> routeInformationParser
-> routerDelegate
-> (setNewRoutePath
)
RouterDelegate
的覆寫案例:
class MyRouterDelegate extends RouterDelegate<PageConfiguration>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<PageConfiguration> {
final List<Page> _pages = [];
final AppState appState;
final GlobalKey<NavigatorState> navigatorKey;
MyRouterDelegate(this.appState) : navigatorKey = GlobalKey() {
appState.addListener(() {
notifyListeners();
});
}
List<MaterialPage> get pages => List.unmodifiable(_pages);
Future<bool> popRoute() {
_removePage(_pages.last);
return Future.value(false);
}
Future<void> setNewRoutePath(PageConfiguration configuration) {
if (shouldAddPage) {
_pages.clear();
addPage(configuration);
}
return SynchronousFuture(null);
}
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
onPopPage: _onPopPage,
pages: buildPages(),
);
}
}
-
MyRouterDelegate
有_pages
屬性眶蕉,這個屬性作為Navigator
的pages
;appState
是狀態(tài)管理的數(shù)據(jù)唧躲,用這個數(shù)據(jù)去驅(qū)動MyRouterDelegate
的觀察者也就是Router
即去重構(gòu)造挽,這樣Navigator
也就會重構(gòu)了。 -
popRoute
將_pages
的最后一個頁面刪掉弄痹,通知Router
即去重構(gòu)饭入,更新Navigator
; -
setNewRoutePath
給_pages
添加對應(yīng)的Page肛真,通知Router
即去重構(gòu)Navigator
谐丢。
BackButtonDispatcher
BackButtonDispatcher
主要就是解決安卓,網(wǎng)頁等物理返回的事件毁欣。它有兩個子類RootBackButtonDispatcher
和ChildBackButtonDispatcher
可以解決Router的嵌套問題庇谆。
BackButtonDispatcher
的返回處理可以直接交給RouterDelegate
去處理岳掐,例如下面的邏輯:
class MyBackButtonDispatcher extends RootBackButtonDispatcher {
final MyRouterDelegate _routerDelegate;
MyBackButtonDispatcher(this._routerDelegate)
: super();
// 3
@override
Future<bool> didPopRoute() {
return _routerDelegate.popRoute();
}
}
最后總結(jié)
總結(jié)
Navigator 2.0的功能更加強大了凭疮,使用方式也變得更加Flutter了。但是變得更復(fù)雜了串述,這樣對學(xué)習(xí)和使用成本造成了很大的困擾执解,這方面也是很多人認(rèn)為Navigator 2.0是一個失敗的改造的原因。
本文主要從源碼角度分析了Navigator 2.0的實現(xiàn)邏輯纲酗,原理清楚后寫代碼應(yīng)該還是很簡單的衰腌。
如果你需要Demo,可以參閱下面兩篇文章的代碼觅赊,特別是第一篇文章的代碼非常具有參考價值: