1.背景與動(dòng)機(jī)
在Navigator 2.0
推出之前乖坠,Flutter
主要通過Navigator 1.0
和其提供的 API(如push()
, pop()
, pushNamed()
等)來管理頁面路由歌焦。然而端考,Navigator 1.0
存在一些局限性颠印,如難以實(shí)現(xiàn)復(fù)雜的頁面操作(如移除棧內(nèi)中間頁面、交換頁面等)洪乍、不支持嵌套路由以及無法滿足全平臺(尤其是Web
平臺)的新需求狂塘。因此,Flutter
官方團(tuán)隊(duì)決定對路由系統(tǒng)進(jìn)行改造呼伸,推出了Navigator 2.0
身冀。
2.主要特性
-
聲明式API
Navigator 2.0
提供的聲明式API
使得路由管理更加直觀和易于理解。開發(fā)者只需聲明頁面的配置信息括享,而無需編寫復(fù)雜的導(dǎo)航邏輯代碼搂根。這種方式不僅減少了代碼量,還提高了代碼的可讀性和可維護(hù)性铃辖。 -
嵌套路由
Navigator 2.0
滿足了嵌套路由的需求場景剩愧,允許開發(fā)者在應(yīng)用中創(chuàng)建嵌套的路由結(jié)構(gòu)。這使得應(yīng)用的結(jié)構(gòu)更加清晰娇斩,同時(shí)也提高了頁面導(dǎo)航的靈活性仁卷。 -
全平臺支持
Navigator 2.0
提供的API
能夠滿足不同平臺(如iOS
穴翩、Android
、Web
等)的導(dǎo)航需求锦积,使得開發(fā)者能夠更加方便地構(gòu)建跨平臺的應(yīng)用芒帕。 -
強(qiáng)大的頁面操作能力
Navigator 2.0
提供了更加豐富的頁面操作能力,如移除棧內(nèi)中間頁面丰介、交換頁面等背蟆。這些操作在Navigator 1.0
中很難實(shí)現(xiàn)或需要編寫復(fù)雜的代碼,而在Navigator 2.0
中則變得簡單直接哮幢。
3.核心組件
-
Router
在Navigator 2.0
中带膀,Router
組件是路由管理的核心。它負(fù)責(zé)根據(jù)當(dāng)前的路由信息(RouteInformation
)和路由信息解析器(RouteInformationParser
)來構(gòu)建和更新UI
橙垢。Router
組件接收三個(gè)主要參數(shù):
1.routeInformationProvider:提供當(dāng)前的路由信息垛叨。
2.routeInformationParser:將路由信息解析為路由配置。
3.routerDelegate:根據(jù)路由配置構(gòu)建和更新UI
柜某。 -
RouteInformationProvider
RouteInformationProvider
是一個(gè)提供當(dāng)前路由信息的組件嗽元。它通常與平臺相關(guān)的路由信息源(如瀏覽器的URL
、Android
的Intent
等)集成喂击,以獲取當(dāng)前的路由信息还棱。 -
RouteInformationParser
RouteInformationParser
負(fù)責(zé)將RouteInformation
解析為RouteConfiguration
。這個(gè)過程允許開發(fā)者根據(jù)路由信息的格式(如URL
)來定義如何將其映射到應(yīng)用內(nèi)的路由配置惭等。 -
RouterDelegate
RouterDelegate
是與UI
構(gòu)建緊密相關(guān)的組件。它必須實(shí)現(xiàn)RouterDelegate
接口办铡,并提供兩個(gè)主要方法:
1.build(BuildContext context):根據(jù)當(dāng)前的路由配置構(gòu)建UI
辞做。
2.setNewRoutePath(List<RouteSettings> configuration):設(shè)置新的路由路徑,并更新UI
寡具。
3.Future<bool> popRoute() :實(shí)現(xiàn)后退邏輯秤茅。
4.簡單實(shí)例
首先通過MaterialApp.router()
來創(chuàng)建MaterialApp
:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final routerDelegate = MyRouterDelegate();
final routeInformationParser = MyRouteInformationParser();
return MaterialApp.router(
title: 'Flutter Navigator 2.0 Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routerDelegate: routerDelegate,
routeInformationParser: routeInformationParser,
);
}
}
需要定義一個(gè)RouterDelegate
對象和一個(gè)RouteInformationParser
對象。其中根據(jù)路由配置構(gòu)建和更新UI
童叠,RouteInformationParser
負(fù)責(zé)將RouteInformation
解析為RouteConfiguration
框喳。
RouterDelegate
可以傳個(gè)泛型,定義其currentConfiguration
對象的類型厦坛。
class MyRouterDelegate extends RouterDelegate<String>
with PopNavigatorRouterDelegateMixin<String>, ChangeNotifier {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
private List<String> _pages = ['/home'];
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
pages: _pages.map((route) => MaterialPage(
key: Key(route),
child: generatePage(route),
)).toList(),
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
_pages.removeLast();
notifyListeners();
return true;
},
);
}
@override
Future<void> setNewRoutePath(String path) async {
if (!_pages.contains(path)) {
_pages.add(path);
notifyListeners();
}
}
Widget generatePage(String route) {
switch (route) {
case '/home':
return HomePage();
case '/details':
// 這里可以傳遞參數(shù)五垮,例如 DetailsPage(arguments: someData)
return DetailsPage();
default:
return NotFoundPage();
}
}
@override
String get currentConfiguration => _pages.last;
}
其中build()
一般返回的是一個(gè)Navigator
對象,popRoute()
實(shí)現(xiàn)后退邏輯杜秸,setNewRoutePath()
實(shí)現(xiàn)新頁面的邏輯放仗。
定義了一個(gè)_pages
數(shù)組對象,記錄每個(gè)路由的path
撬碟,可以理解為是一個(gè)路由棧诞挨,這個(gè)路由棧對我們來說非常友好莉撇,在有復(fù)雜的業(yè)務(wù)邏輯時(shí),我們可以自行定義相應(yīng)的棧管理邏輯惶傻。currentConfiguration
返回的是棧頂?shù)?code>page信息棍郎。
創(chuàng)建一個(gè)類繼承RouteInformationParser
,主要的作用是包裝解析路由信息银室,這里有一個(gè)最簡單的方式涂佃,如下:
class MyRouteInformationParser extends RouteInformationParser<String> {
@override
Future<String> parseRouteInformation(RouteInformation routeInformation) {
final uri = Uri.parse(routeInformation.location);
return SynchronousFuture(uri.path);
}
@override
RouteInformation restoreRouteInformation(String configuration) {
return RouteInformation(location: configuration);
}
}
好的,接下來我們看一下調(diào)用:
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home')),
body: Center(
child: ElevatedButton(
onPressed: () {
Router.of(context).routerDelegate.setNewRoutePath("/details");
},
child: Text('Go to Details'),
),
),
);
}
}
class DetailsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Details')),
body: Center(
child: Text('This is Details Page'),
),
);
}
}
class NotFoundPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Not Found')),
body: Center(
child: Text('Page not found'),
),
);
}
}
非常簡單粮揉,直接調(diào)用Router.of(context).routerDelegate.setNewRoutePath()
即可巡李。
到此為止,一個(gè)使用Navigator2.0
的最簡單的路由實(shí)例就完成了扶认。和Navigator1.0
相比侨拦,看上去繁雜了不少。但是可以根據(jù)業(yè)務(wù)需求自定義路由棧進(jìn)行管理辐宾,大大的提升了靈活性狱从。接來看我們看一下Navigator2.0
是如何對路由進(jìn)行實(shí)現(xiàn)的。
5.源碼簡析
我們在使用Navigator2.0
時(shí)叠纹,是通過MaterialApp.router()
創(chuàng)建的MaterialApp
對象季研,之前章節(jié)提到過,傳了RouteInformationParser
和RouterDelegate
這兩個(gè)對象誉察。當(dāng)傳遞了RouterDelegate
對象時(shí)与涡,_MaterialAppState
中的_usesRouter
會(huì)被設(shè)置為true
。
bool get _usesRouter => widget.routerDelegate != null || widget.routerConfig != null;
在build()
時(shí)持偏,通過WidgetsApp.router()
方法創(chuàng)建了一個(gè)WidgetsApp
對象:
if (_usesRouter) {
return WidgetsApp.router(
key: GlobalObjectKey(this),
routeInformationProvider: widget.routeInformationProvider,
routeInformationParser: widget.routeInformationParser,
routerDelegate: widget.routerDelegate,
routerConfig: widget.routerConfig,
backButtonDispatcher: widget.backButtonDispatcher,
builder: _materialBuilder,
title: widget.title,
onGenerateTitle: widget.onGenerateTitle,
textStyle: _errorTextStyle,
color: materialColor,
locale: widget.locale,
localizationsDelegates: _localizationsDelegates,
localeResolutionCallback: widget.localeResolutionCallback,
localeListResolutionCallback: widget.localeListResolutionCallback,
supportedLocales: widget.supportedLocales,
showPerformanceOverlay: widget.showPerformanceOverlay,
checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
showSemanticsDebugger: widget.showSemanticsDebugger,
debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
inspectorSelectButtonBuilder: _inspectorSelectButtonBuilder,
shortcuts: widget.shortcuts,
actions: widget.actions,
restorationScopeId: widget.restorationScopeId,
);
}
在_WidgetsAppState
中根據(jù)routerDelegate
設(shè)置了成員變量_usesRouterWithDelegates
的值:
bool get _usesRouterWithDelegates => widget.routerDelegate != null;
在build()
時(shí)會(huì)創(chuàng)建一個(gè)Router
對象驼卖,其中Router
繼承了StatefulWidget
:
@override
Widget build(BuildContext context) {
Widget? routing;
if (_usesRouterWithDelegates) {
routing = Router<Object>(
restorationScopeId: 'router',
routeInformationProvider: _effectiveRouteInformationProvider,
routeInformationParser: widget.routeInformationParser,
routerDelegate: widget.routerDelegate!,
backButtonDispatcher: _effectiveBackButtonDispatcher,
);
}
......
}
在上一章節(jié)的實(shí)例中我們可得知,頁面的切換都是依靠RouterDelegate
對象進(jìn)行的鸿秆。
每當(dāng)切換到新的頁面時(shí)酌畜,都會(huì)調(diào)用setNewRoutePath()
方法,因此我們來看一下setNewRoutePath()
是什么時(shí)候被調(diào)用的卿叽,有兩處:
第一處:
void _handleRouteInformationProviderNotification() {
_routeParsePending = true;
_processRouteInformation(widget.routeInformationProvider!.value, () => widget.routerDelegate.setNewRoutePath);
}
_RouteSetter<T> _processParsedRouteInformation(Object? transaction, ValueGetter<_RouteSetter<T>> delegateRouteSetter) {
return (T data) async {
if (_currentRouterTransaction != transaction) {
return;
}
await delegateRouteSetter()(data);
if (_currentRouterTransaction == transaction) {
_rebuild();
}
};
}
我們看看_handleRouteInformationProviderNotification
的調(diào)用時(shí)機(jī):
@override
void initState() {
super.initState();
widget.routeInformationProvider?.addListener(_handleRouteInformationProviderNotification);
widget.backButtonDispatcher?.addCallback(_handleBackButtonDispatcherNotification);
widget.routerDelegate.addListener(_handleRouterDelegateNotification);
}
我們可以看到在initState()
時(shí)桥胞,也就是在Router
被初始化的時(shí)候由widget.routeInformationProvider
來監(jiān)聽一些狀態(tài)實(shí)現(xiàn)新頁面的切換。我們來看一下routeInformationProvider
考婴。RouteInformationProvider
在我們自己沒有創(chuàng)建的情況下贩虾,系統(tǒng)會(huì)默認(rèn)為我們創(chuàng)建一個(gè)PlatformRouteInformationProvider
對象。它實(shí)際上是個(gè)ChangeNotifier
沥阱。系統(tǒng)會(huì)監(jiān)聽每一幀的信號發(fā)送整胃,調(diào)用其父類routerReportsNewRouteInformation()
方法,我們看看它的實(shí)現(xiàn):
@override
void routerReportsNewRouteInformation(RouteInformation routeInformation, {RouteInformationReportingType type = RouteInformationReportingType.none}) {
final bool replace =
type == RouteInformationReportingType.neglect ||
(type == RouteInformationReportingType.none &&
_equals(_valueInEngine.uri, routeInformation.uri));
SystemNavigator.selectMultiEntryHistory();
SystemNavigator.routeInformationUpdated(
uri: routeInformation.uri,
state: routeInformation.state,
replace: replace,
);
_value = routeInformation;
_valueInEngine = routeInformation;
}
其中SystemNavigator.selectMultiEntryHistory()
的實(shí)現(xiàn)如下:
/// Selects the multiple-entry history mode.
///
/// On web, this switches the browser history model to one that tracks all
/// updates to [routeInformationUpdated] to form a history stack. This is the
/// default.
///
/// Currently, this is ignored on other platforms.
///
/// See also:
///
/// * [selectSingleEntryHistory], which forces the history to only have one
/// entry.
static Future<void> selectMultiEntryHistory() {
return SystemChannels.navigation.invokeMethod<void>('selectMultiEntryHistory');
}
這個(gè)方法是由各個(gè)平臺自行實(shí)現(xiàn)的。從注釋中我們可得知如果是在Web
平臺下屁使,它會(huì)切換成history
模式在岂,并從history stack
中追蹤所有的變化。 在history
發(fā)生變化時(shí)蛮寂,會(huì)發(fā)送信號給Flutter
層等待處理蔽午。SystemNavigator.routeInformationUpdated()
方法是用來更新路由的,我們先不做分析酬蹋。
接著我們回到PlatformRouteInformationProvider
及老,看看它什么時(shí)候會(huì)執(zhí)行notifyListeners()
方法:
@override
Future<bool> didPushRouteInformation(RouteInformation routeInformation) async {
assert(hasListeners);
_platformReportsNewRouteInformation(routeInformation);
return true;
}
void _platformReportsNewRouteInformation(RouteInformation routeInformation) {
if (_value == routeInformation) {
return;
}
_value = routeInformation;
_valueInEngine = routeInformation;
notifyListeners();
}
在監(jiān)聽到有push
路由的情況下時(shí),會(huì)調(diào)用notifyListeners()
范抓,從而實(shí)現(xiàn)頁面的切換骄恶。
我們再來看第二處調(diào)用setNewRoutePath()
的地方:
@override
void didChangeDependencies() {
_routeParsePending = true;
super.didChangeDependencies();
// The super.didChangeDependencies may have parsed the route information.
// This can happen if the didChangeDependencies is triggered by state
// restoration or first build.
if (widget.routeInformationProvider != null && _routeParsePending) {
_processRouteInformation(widget.routeInformationProvider!.value, () => widget.routerDelegate.setNewRoutePath);
}
_routeParsePending = false;
_maybeNeedToReportRouteInformation();
}
void _processRouteInformation(RouteInformation information, ValueGetter<_RouteSetter<T>> delegateRouteSetter) {
assert(_routeParsePending);
_routeParsePending = false;
_currentRouterTransaction = Object();
widget.routeInformationParser!
.parseRouteInformationWithDependencies(information, context)
.then<void>(_processParsedRouteInformation(_currentRouterTransaction, delegateRouteSetter));
}
parseRouteInformationWithDependencies()
方法中調(diào)用的parseRouteInformation()
其實(shí)就是我們自定義RouteInformationParser
來進(jìn)行的實(shí)現(xiàn)。
Future<T> parseRouteInformationWithDependencies(RouteInformation routeInformation, BuildContext context) {
return parseRouteInformation(routeInformation);
}
看到當(dāng)其與父的依賴關(guān)系被改變的時(shí)候會(huì)調(diào)用setNewRoutePath()
匕垫。大概率就是App
初始化的時(shí)候被調(diào)用一次僧鲁。
6.根據(jù)狐友業(yè)務(wù)的Web端實(shí)踐
我們的Flutter
團(tuán)隊(duì)會(huì)承擔(dān)一些運(yùn)營活動(dòng)的H5
需求。在實(shí)現(xiàn)時(shí)我們對路由有如下需求:
1.可以根據(jù)業(yè)務(wù)自由的管理路由棧象泵。
2.分享鏈接只能分享出去默認(rèn)入口鏈接寞秃,不希望中間的路由鏈接被分享出去。
3.不管有多少個(gè)路由頁面偶惠,history
始終不變春寿,在響應(yīng)瀏覽器返回鍵時(shí)不響應(yīng)路由棧的pop
操作。
在之前使用Navigator1.0
時(shí)體驗(yàn)并不太好忽孽,一個(gè)是不夠靈活绑改,另外還需對分享出去的鏈接做處理。因此我們利用Navigator2.0
設(shè)計(jì)了一套新的路由:
MyRouterDelegate delegate = MyRouterDelegate();
@override
Widget build(BuildContext context) {
return MaterialApp.router(
debugShowCheckedModeBanner: false,
routeInformationParser: MyRouteParser(),
routerDelegate: delegate,
);
}
Parser
實(shí)現(xiàn)非常簡單:
class MyRouteParser extends RouteInformationParser<RouteSettings> {
@override
///parseRouteInformation() 方法的作用就是接受系統(tǒng)傳遞給我們的路由信息 routeInformation
Future<RouteSettings> parseRouteInformation(
RouteInformation routeInformation) {
// Uri uri = Uri.parse(routeInformation.location??"/");
return SynchronousFuture(RouteSettings(name: routeInformation.location));
}
@override
///恢復(fù)路由信息
RouteInformation restoreRouteInformation(RouteSettings configuration) {
return RouteInformation(location: configuration.name);
}
}
Delegate
的實(shí)現(xiàn)如下:
import 'package:ai_chatchallenge/router/exit_util.dart';
import 'package:ai_chatchallenge/router/navigator_util.dart';
import 'package:ai_chatchallenge/router/my_router_arg.dart';
import 'package:flutter/material.dart';
import 'route_page_config.dart';
class MyRouterDelegate extends RouterDelegate<RouteSettings>
with PopNavigatorRouterDelegateMixin<RouteSettings>, ChangeNotifier {
///頁面棧
List<Page> _stack = [];
//當(dāng)前的界面信息
RouteSettings _setting = RouteSettings(
name: RouterName.rootPage,
arguments: BaseArgument()..name = RouterName.rootPage);
//重寫navigatorKey
@override
GlobalKey<NavigatorState> navigatorKey;
MyRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>() {
//初始化兩個(gè)方法 一個(gè)是push頁面 另一個(gè)是替換頁面
NavigatorUtil()
.registerRouteJump(RouteJumpFunction(onJumpTo: (RouteSettings setting) {
// _setting = setting;
// changePage();
addPage(name: setting.name, arguments: setting.arguments);
}, onReplaceAndJumpTo: (RouteSettings setting) {
if (_stack.isNotEmpty) {
_stack.removeLast();
}
_setting = setting;
changePage();
}, onClearStack: () {
_stack.clear();
_setting = RouteSettings(
name: RouterName.rootPage,
arguments: BaseArgument()..name = RouterName.rootPage);
changePage();
}, onBack: () {
if (_stack.isNotEmpty) {
_stack.removeLast();
if (_stack.isNotEmpty) {
_setting = _stack.last;
} else {
_setting = RouteSettings(
name: RouterName.rootPage,
arguments: BaseArgument()..name = RouterName.rootPage);
}
changePage();
}
}));
}
@override
RouteSettings? get currentConfiguration {
return _stack.last;
}
@override
Future<bool> popRoute() {
if (_stack.length > 1) {
_stack.removeLast();
_setting = _stack.last;
changePage();
//非最后一個(gè)頁面
return Future.value(true);
}
//最后一個(gè)頁面確認(rèn)退出操作
return _confirmExit();
}
Future<bool> _confirmExit() async {
bool result = ExitUtil.doubleCheckExit(navigatorKey.currentContext!);
// bool result = await ExitUtil.backToDesktop();
return !result;
}
void addPage({required name, arguments}) {
_setting = RouteSettings(name: name, arguments: arguments);
changePage();
}
@override
Widget build(BuildContext context) {
return WillPopScope(
//解決物理返回建無效的問題
onWillPop: () async => !await navigatorKey.currentState!.maybePop(),
child: Navigator(
key: navigatorKey,
pages: _stack,
onPopPage: _onPopPage,
),
);
}
/// 按下返回的回調(diào)
bool _onPopPage(Route<dynamic> route, dynamic result) {
debugPrint("這里的試試");
if (!route.didPop(result)) {
return false;
}
return true;
}
changePage() {
int index = getCurrentIndex(_stack, _setting!);
List<Page> tempPages = _stack;
if (index != -1) {
// 要求棧中只允許有一個(gè)同樣的頁面的實(shí)例 否則開發(fā)模式熱更新會(huì)報(bào)錯(cuò)
// 要打開的頁面在棧中已存在兄一,則將該頁面和它上面的所有頁面進(jìn)行出棧
tempPages = tempPages.sublist(0, index);
// 或者刪除之前存在棧里的頁面,重新創(chuàng)建
// tempPages.removeAt(index);
}
Page page;
if (_setting?.arguments is BaseArgument) {
if ((_setting?.arguments as BaseArgument).name == RouterName.rootPage) {
_stack.clear();
}
} else {
if (_setting?.name == RouterName.rootPage) {
_stack.clear();
}
}
page = buildPage(name: _setting?.name, arguments: _setting?.arguments);
tempPages = [...tempPages, page];
NavigatorUtil().notify(tempPages, _stack);
_stack = tempPages;
notifyListeners();
}
@override
Future<void> setInitialRoutePath(RouteSettings configuration) {
return super.setInitialRoutePath(_setting);
}
@override
Future<void> setNewRoutePath(RouteSettings configuration) async {
if (configuration.arguments is BaseArgument) {
if ((configuration.arguments as BaseArgument).name ==
RouterName.rootPage) {
_stack.clear();
}
} else {
if (configuration.name == RouterName.rootPage) {
_stack.clear();
}
}
addPage(name: configuration.name, arguments: configuration.arguments);
}
}
其中_stack
是我們的路由棧绢淀,_setting
是RouteSettings
,每執(zhí)行一個(gè)新的路由跳轉(zhuǎn)瘾腰,都會(huì)創(chuàng)建一個(gè)RouteSettings
對象并賦值給_setting
,最終在插入_stack
里覆履。buildPage()
的實(shí)現(xiàn)如下:
//建造頁面
buildPage({required name, arguments}) {
return MaterialPage(
child: getPageChild(name: name, arguments: arguments),
arguments: arguments,
name: name,
key: ValueKey(
arguments is BaseArgument ? (arguments as BaseArgument).name : name));
}
其中MaterialPage
繼承了Page
蹋盆。getPageChild()
實(shí)現(xiàn)如下:
Widget getPageChild({required name, arguments}) {
Widget page;
Map? arg;
if (arguments is Map) {
arg = arguments;
}
if (arguments is BaseArgument) {
switch ((arguments as BaseArgument).name) {
case RouterName.rootPage:
page = TestHomePage();
break;
case RouterName.testChild1Page:
page = TestChildPage1(
argument: arguments.arguments as TestChild1PageArgument,
);
break;
case RouterName.testChild2Page:
page = TestChildPage2();
break;
default:
page = TestHomePage();
}
} else {
page = TestHomePage();
}
return page;
}
class RouterName {
static const rootPage = "/";
static const testChild1Page = "/testChild1Page";
static const testChild2Page = "/testChild2Page";
}
我們可以看到,在真正返回Widget
時(shí)硝全,我們并沒有使用傳入的name
參數(shù)栖雾,而是BaseArgument
的name
參數(shù),這是為什么呢伟众?這是在于我們?yōu)榱藢?shí)現(xiàn)無論頁面怎么跳轉(zhuǎn)析藕,從頭到尾瀏覽器只保留一個(gè)history
,因此我們在頁面跳轉(zhuǎn)時(shí)RouteSettings
的name
并不發(fā)生變化凳厢,通過其arguments
里面的參數(shù)變化返回不同的Widget
账胧。這樣在路由跳轉(zhuǎn)時(shí)竞慢,其實(shí)MaterialPage
由于name
一直會(huì)被直接復(fù)用,從而不會(huì)創(chuàng)建新的MaterialPage
也就不會(huì)產(chǎn)生history
治泥。
NavigatorUtil
是由業(yè)務(wù)調(diào)用的筹煮,創(chuàng)建跳轉(zhuǎn)方法的抽象類,提供了onJumpTo()
居夹,onReplaceAndJumpTo()
败潦,onClearStack()
,onBack()
四個(gè)方法供業(yè)務(wù)調(diào)用准脂,我們可以看一下onJumpTo()
的實(shí)現(xiàn):
@override
void onJumpTo(
{required name,
Object? stackArguments,
Map<String, dynamic>? historyArgMap,
BuildContext? context}) {
var arg = BaseArgument();
arg.name = name;
arg.arguments = stackArguments;
RouteSettings settings =
RouteSettings(name: RouterName.rootPage, arguments: arg);
return _function!.onJumpTo!(settings);
}
可以看到在創(chuàng)建RouteSettings
對象時(shí)劫扒,name
為RouterName.rootPage
,arg
時(shí)由業(yè)務(wù)傳的真正的跳轉(zhuǎn)頁面相關(guān)的參數(shù)狸膏。
我們看一下業(yè)務(wù)的調(diào)用:
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Column(
children: [
Text("TestHomePage"),
Text("history length is : " + window.history.length.toString()),
Text("href: " + WebUtil.get().getWindow().location.href),
TextButton(
onPressed: () {
var arg = TestChild1PageArgument()..isSuccess = "false";
NavigatorUtil().onJumpTo(
name: RouterName.testChild1Page,
stackArguments: arg,
historyArgMap: arg.toJson(),
context: context);
},
child: Text("Go to TestChildPage1"))
],
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Column(
children: [
Text("TestChildPage1"),
Text("history length is : " + window.history.length.toString()),
Text("href: " + WebUtil.get().getWindow().location.href),
TextButton(
onPressed: () {
NavigatorUtil().onJumpTo(
name: RouterName.testChild2Page, context: context);
},
child: Text("Go to TestChildPage2")),
TextButton(
onPressed: () {
NavigatorUtil().onBack();
},
child: Text("Back to TestHomePage")),
],
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Column(
children: [
Text("TestChildPage2"),
Text("history length is : " + window.history.length.toString()),
Text("href: " + WebUtil.get().getWindow().location.href),
TextButton(
onPressed: () {
NavigatorUtil().onBack();
},
child: Text("Back to TestChild1page")),
TextButton(
onPressed: () {
NavigatorUtil().onClearStack();
},
child: Text("Back to Root")),
],
),
),
);
}
我們看一下截圖展示:
在這個(gè)過程中href
不會(huì)發(fā)生變化沟饥,history
也不會(huì)發(fā)生變化,完全符合我們的預(yù)期环戈。
6.總結(jié)
Flutter
的Navigator 2.0
引入了聲明式的API
闷板,使頁面路由管理更加靈活和強(qiáng)大。相較于Navigator 1.0
院塞,Navigator 2.0
支持更復(fù)雜的路由操作遮晚,如嵌套路由和動(dòng)態(tài)路由配置。它使用不可變的Page
對象列表來表示路由歷史拦止,與Flutter
的不可變Widgets
設(shè)計(jì)理念一致县遣。Navigator 2.0
還支持命名路由,通過簡單的路由名稱即可實(shí)現(xiàn)頁面跳轉(zhuǎn)汹族,大大簡化了路由管理的復(fù)雜度萧求。此外,它還提供了更豐富的路由回調(diào)和狀態(tài)管理功能顶瞒,使開發(fā)者能夠更輕松地構(gòu)建復(fù)雜的Flutter
應(yīng)用夸政。