Flutter Navigator 2.0 使用宣告式(declarative style)的方式定義路由導覽的方式,需要事先具備基本概念:
Navigator
透過RouterDelegate
的pages
設定顯示對應畫面RouteInformation
路由資訊解析成自定義的路由設定類別(T)RouteInformation
的來源RouteInformationParser
提供的路由設定管理pages
的內容Router
點擊返回按鈕
的事件引用:Flutter Navigator 2.0 and Deep Links (Kevin D Moore, Feb 23 2021)
www.raywenderlich.com
我們需要實作 RouterDelegate
與 RouteInformationParser
這兩個抽象類別。
class MyApp extends StatefulWidget {
@override
_MyApp createState() => _MyApp();
}
class _MyApp extends State<MyApp> {
AppRouterDelegate _delegate = AppRouterDelegate();
AppRouterParser _parser = AppRouterParser();
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: "Navigator v2",
routerDelegate: _delegate,
routeInformationParser: _parser,
);
}
}
App在啟動的時候,會使用 RouteInformationParser
解析要處理的路徑,在parseRouteInformation
方法裡我們會收到 RouteInfomationProvider
提供的 RouteInformation
。
@override
Future<RoutePage> parseRouteInformation(RouteInformation routeInformation) {
print(
'RouteInformationParser parseRouteInformation ${routeInformation.location}');
RoutePage configuration =
getRoutePage(name: routeInformation.location ?? '/');
return SynchronousFuture(configuration);
}
@override
RouteInformation restoreRouteInformation(RoutePage configuration) {
print(
'RouteInformationParser restoreRouteInformation ${configuration.path}');
return RouteInformation(location: configuration.path ?? '/');
}
其中泛型<T>
可以自行定義想要的類別(RoutePage
),這邊簡單的使用一個函數處理對應的路由設定並綁定 widget 內容。
class RoutePage extends RouteSettings {
final Widget? widget;
const RoutePage({
required String name,
Object? arguments,
this.widget,
}) : super(name: name, arguments: arguments);
}
RoutePage getRoutePage({required String name}) {
print("getRoutePage $name");
switch (name) {
case '/':
return RoutePage(name: name, widget: HomePage());
case '/items':
return RoutePage(name: name, widget: ItemsPage());
default:
return RoutePage(name: '404', widget: UnknownPage());
}
}
接著宣告 RouterDelegate
必要的屬性 pages
class AppRouterDelegate extends RouterDelegate<RoutePage>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<RoutePage> {
final List<Page> _pages = [];
@override
final GlobalKey<NavigatorState> navigatorKey;
...
}
定義setNewRoutePath
行為:RouteInformationParser
解析完路由會接著呼叫setNewRoutePath
方法並帶入自定義的路由設定泛型(RoutePage)。
我們可以在這邊定義收到新的路由設定時要如何操作pages
的內容。
@override
Future<void> setNewRoutePath(RoutePage configuration) {
print("RouterDelegate setNewRoutePath ${configuration.name}");
final shouldAddPage = _pages.isEmpty ||
(_pages.last.arguments as RoutePage).name != configuration.name;
if (shouldAddPage) {
replace(configuration);
}
return SynchronousFuture(null);
}
RouterDelegate
會透過 build 方法重構 Navigator
。Navigator
提供了底層路由的架構,Navigator 在透過 Overlay
將 pages
的內容渲染在畫面上。
@override
Widget build(BuildContext context) {
print("RouterDelegate build");
return Navigator(
key: navigatorKey,
onPopPage: _onPopPage,
pages: List.of(_pages),
);
}
還需要覆寫 RouterDelegate
的 currentConfiguration
,在呼叫完 build 後,程式會呼叫RouteInformationParser
的 restoreRouteInformation
,用來將路由設定寫回記錄(可參考 browser history 的行為)。
@override
RoutePage get currentConfiguration {
print("RouterDelegate currentConfiguration");
return _pages.last.arguments as RoutePage;
}
RouterDelegate
管理 pages
來處理路由的狀態。 pages
只能是 Page
抽像類別的子類別,可以實作相關設定客制化想要的行為,例如過場特效。
對於 pages
異動後,需要呼叫 notifyListeners
通知 Router
重新整理。
replace(RoutePage configuration) {
_pages.clear();
_pages.add(
MaterialPage(
child: configuration.widget,
key: ValueKey(configuration.name),
name: configuration.name,
arguments: configuration,
),
);
notifyListeners();
}
可以比對主控台的訊息,理解呼叫的先後順序。
Restarted application in 166ms.
RouteInformationParser parseRouteInformation /
[RoutePage] getRoutePage /
RouterDelegate setNewRoutePath Home
RouterDelegate build
RouterDelegate currentConfiguration
RouteInformationParser restoreRouteInformation /
為了讓 Navigator
下的子元素可以存取路由的設定,我們可以透過 Router.of
的方式找到父層中的的 delegate
。
static AppRouterDelegate of(BuildContext context) {
RouterDelegate routerDelegate = Router.of(context).routerDelegate;
return routerDelegate as AppRouterDelegate;
}
使用方式如下:
AppRouterDelegate.of(context).pushNamed('/items');
範例中使用兩個畫面測試自己定義的 replace
和 push
方法,這些方法都只是對 pages
進行操作而已。