Flutter中g(shù)o_router路由管理的使用指南
前言
go_router 是一個 Flutter 的第三方路由插件,相比 Flutter 自帶的路由,go_router 更加靈活,而且簡單易用。在 App 應(yīng)用中,如果你想自己控制路由的定義和管理方式,那么它將十分有用。同時,對于 Web 應(yīng)用來說,go_router 也提供了很好的支持。 使用 go_router 后,你可以定義 URL 的格式,使用 URL 跳轉(zhuǎn),處理深度鏈接以及其他一系列的導航相關(guān)的應(yīng)用場景。
GoRouter 特性
GoRouter 針對頁面導航提供了下面這些特性:
- 使用模板語法解析路由路徑和路由查詢(
query)參數(shù); - 支持單個目標路由展示多個頁面(子路由);
- 重定向:可以基于應(yīng)用狀態(tài)跳轉(zhuǎn)到不同的URL,比如用戶沒有登錄時跳轉(zhuǎn)到登錄頁;
- 使用
StatefulShellRoute可以支持嵌套的 Tab 導航; - 同時支持
Material風格和Cupertino風格應(yīng)用; - 兼容
NavigatorAPI 。
添加插件
當前最新版本的 go_router 是10.0.0(6.3.0版本以上需要 Dart 2.18),可以根據(jù)自己的需要添加相應(yīng)的版本。在 pubspec.yaml 中加入依賴的版本即可,下面是以7.1.1版本為例。
dependencies: go_router: ^7.1.1
路由配置
引入 go_router 插件后,就可以在應(yīng)用中配置 GoRouter,代碼如下:
import 'package:go_router/go_router.dart';
// GoRouter configuration
final _router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
name: 'home', // Optional, add name to your routes. Allows you navigate by name instead of path
path: '/',
builder: (context, state) => HomeScreen(),
),
GoRoute(
name: 'page2',
path: '/page2',
builder: (context, state) => Page2Screen(),
),
],
);然后,我們就可以通過MaterialApp.router或CupertinoApp.router構(gòu)造函數(shù)來使用 GoRouter,并且將 routerConfig 參數(shù)設(shè)置為我們前面定義的 GoRouter 配置對象。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router,
);
}
}接下來就可以愉快地玩耍 GoRouter 了。
路由參數(shù)
GoRouter 的每一個路由都通過 GoRoute對象來配置,我們可以在構(gòu)建 GoRoute 對象時來配置路由參數(shù)。路由參數(shù)典型的就是路徑參數(shù),比如 /path/:{路徑參數(shù)},這個時候 GoRoute的路徑參數(shù)和很多 Web 框架的路由是一樣的,通過一個英文冒號加參數(shù)名稱就可以配置,之后我們可以在回調(diào)方法中通過 GoRouterState 對象獲取路徑參數(shù),這個參數(shù)就可以傳遞到路由跳轉(zhuǎn)目的頁面。
GoRoute(
path: '/fruits/:id',
builder: (context, state) {
final id = state.params['id'] // Get "id" param from URL
return FruitsPage(id: id);
},
),同樣,也可以從GoRouterState中獲取 URL 路徑中的查詢(query)參數(shù),例如下面的代碼就是從/fruits?search=antonio中獲取search參數(shù)。
GoRoute(
path: '/fruits',
builder: (context, state) {
final search = state.queryParams['search'];
return FruitsPage(search: search);
},
),添加子路由
路由匹配后可以支持多個頁面(即子路由),當一個新的頁面在舊的頁面之上展示時,這個時候的效果和調(diào)用 push方法是一樣的,。如果頁面提供了 AppBar 組件的話,那么會自動增加返回按鈕。 要使用子路由,我們只需要在上級路由中增加對應(yīng)的下級路由即可,代碼如下。
GoRoute(
path: '/fruits',
builder: (context, state) {
return FruitsPage();
},
routes: <RouteBase>[ // Add child routes
GoRoute(
path: 'fruits-details', // NOTE: Don't need to specify "/" character for router's parents
builder: (context, state) {
return FruitDetailsPage();
},
),
],
)頁面導航
GoRouter 提供了多種方式跳轉(zhuǎn)到目的頁面,比如使用context.go()跳轉(zhuǎn)到指定的 URL 地址。
build(BuildContext context) {
return TextButton(
onPressed: () => context.go('/fruits/fruit-detail'),
);
}也可以使用路由的名稱進行跳轉(zhuǎn),這個時候調(diào)用context.goNamed()即可。
build(BuildContext context) {
return TextButton(
// remember to add "name" to your routes
onPressed: () => context.goNamed('fruit-detail'),
);
}如果要構(gòu)建查詢參數(shù),那么可以使用 Uri 類來構(gòu)建路由路徑。
context.go(
Uri(
path: '/fruit-detail',
queryParameters: {'id': '10'},
).toString(),
);如果要從當前頁面返回的話,調(diào)用context.pop()即可。
嵌套導航
有些應(yīng)用在同一個頁面展示多個子頁面,例如 BottomNavigationBar 在進行導航的時候就可以一直保留在屏幕底部。這種其實是嵌套導航,在 GoRouter 里,可以通過StatefulShellRoute來實現(xiàn)。 StatefulShellRoute不直接使用根導航(root Navigator),而是通過不同的導航的子路由來實現(xiàn)嵌套導航。對于每一個導航分支,都會創(chuàng)建各自獨立的導航,相當于是一個并行導航樹,從而實現(xiàn)了有狀態(tài)的嵌套導航。 當我們使用BottomNavigationBar的時候,就可以為很方便地為每一個 Tab 配置一個持久的導航狀態(tài)。 StatefulShellRoute通過指定一個StatefulShellBranch類型的列表來完成,列表每一個元素代表路由樹的一個獨立的有狀態(tài)的分支。StatefulShellBranch為每個分支提供了根路由和 Navigator key(GlobalKey),并提供了可選的初始默認路由地址。 我們來看看具體怎么實現(xiàn)。 首先創(chuàng)建我們的 GoRouter 對象,這個時候我們需要添加StatefulShellRoute.indexedStack到路由中,這個類負責創(chuàng)建嵌套路由。StatefulShellRoute.indexedStack() 實際上是使用了 IndexedStack創(chuàng)建了一個StatefulShellRoute。 這個構(gòu)造函數(shù)使用IndexedStack來管理每個分支導航的頁面,示例代碼如下:
// Create keys for `root` & `section` navigator avoiding unnecessary rebuilds
final _rootNavigatorKey = GlobalKey<NavigatorState>();
final _sectionNavigatorKey = GlobalKey<NavigatorState>();
final router = GoRouter(
navigatorKey: _rootNavigatorKey,
initialLocation: '/feed',
routes: <RouteBase>[
StatefulShellRoute.indexedStack(
builder: (context, state, navigationShell) {
// Return the widget that implements the custom shell (e.g a BottomNavigationBar).
// The [StatefulNavigationShell] is passed to be able to navigate to other branches in a stateful way.
return ScaffoldWithNavbar(navigationShell);
},
branches: [
// The route branch for the 1o Tab
StatefulShellBranch(
navigatorKey: _sectionNavigatorKey,
// Add this branch routes
// each routes with its sub routes if available e.g feed/uuid/details
routes: <RouteBase>[
GoRoute(
path: '/feed',
builder: (context, state) => const FeedPage(),
routes: <RouteBase>[
GoRoute(
path: 'detail',
builder: (context, state) => const FeedDetailsPage(),
)
],
),
],
),
// The route branch for 2o Tab
StatefulShellBranch(routes: <RouteBase>[
// Add this branch routes
// each routes with its sub routes if available e.g shope/uuid/details
GoRoute(
path: '/shope',
builder: (context, state) => const ShopePage(),
),
])
],
),
],
);在上面的代碼中,我們在路由中加入了StatefulShellRoute.indexedStack(),由它負責創(chuàng)建路由分支以及返回一個自定義的導航殼,這里是BottomNavigationBar。
- 在 builder 參數(shù)中, 我們返回導航用的殼,一個簡單的帶有
BottomNavigationBar的Scaffold,這里需要記得將navigationShell傳給頁面,因為我們需要用它來導航到其他分支,例如從Home到 Shope。 - 在路由分支數(shù)組
branches中,我們提供了一個StatefulShellBranch的數(shù)組。這里只需要給第一個元素的navigatorKey提供之前創(chuàng)建的全局的_sectionNavigatorKey。其他分支則使用默認的 key。同時,為每個分支提供了一個RouteBase列表,該列表是對應(yīng)分支的路由。
下面是我們定義的帶有BottomNavigationBar的自定義導航殼的代碼。
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class ScaffoldWithNavbar extends StatelessWidget {
const ScaffoldWithNavbar(this.navigationShell, {super.key});
/// The navigation shell and container for the branch Navigators.
final StatefulNavigationShell navigationShell;
@override
Widget build(BuildContext context) {
return Scaffold(
body: navigationShell,
bottomNavigationBar: BottomNavigationBar(
currentIndex: navigationShell.currentIndex,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.shop), label: 'Shope'),
],
onTap: _onTap,
),
);
}
void _onTap(index) {
navigationShell.goBranch(
index,
// A common pattern when using bottom navigation bars is to support
// navigating to the initial location when tapping the item that is
// already active. This example demonstrates how to support this behavior,
// using the initialLocation parameter of goBranch.
initialLocation: index == navigationShell.currentIndex,
);
}
}在上面的代碼中,實際上就是構(gòu)建帶有BottomNavigationBar的Scaffold然后 body 是從路由里獲取的navigationShell. 路由分支及頁面的切換通過_onTap(index) 實現(xiàn),當點擊某個 Tab 時,就使用navigationShell.goBranch(index)來完成切換動作。完整代碼:flutter-go_router-with-nested-tab-navigation。
路由守衛(wèi)(Guards)
有些路由地址需要守衛(wèi),例如對于沒有登錄的用戶,有些頁面就無法訪問。GoRouter 可以設(shè)置全局的重定向路由。最常見的一個場景就是對于沒有登錄的用戶,跳轉(zhuǎn)到/login 登錄頁面。 在 GoRouter 中,可以通過redirect 參數(shù)配置重定向,這是一個GoRouterRedirect的回調(diào)方法。如果要基于應(yīng)用狀態(tài)更改跳轉(zhuǎn)都只,那么既可以在GoRouter或GoRoute的構(gòu)造方法中增加redirect參數(shù)。其中 GoRoute是只針對當前路由進行跳轉(zhuǎn)處理,而GoRouter這是全局處理。下面是示例代碼,如果不需要跳轉(zhuǎn),則在回調(diào)方法中返回 null即可。
GoRouter(
redirect: (BuildContext context, GoRouterState state) {
final isAuthenticated = // your logic to check if user is authenticated
if (!isAuthenticated) {
return '/login';
} else {
return null; // return "null" to display the intended route without redirecting
}
},
...也可以指定一個redirectLimit參數(shù)來限制最大的跳轉(zhuǎn)次數(shù),這個值默認是5。如果超過了跳轉(zhuǎn)次數(shù),則會顯示一個錯誤頁面。
轉(zhuǎn)場動畫
GoRouter支持為每個 GoRoute自定義轉(zhuǎn)場動畫,這可以通過GoRoute的構(gòu)造函數(shù)的pageBuilder 參數(shù)來完成,下面是示例代碼。
GoRoute(
path: '/fruit-details',
pageBuilder: (context, state) {
return CustomTransitionPage(
key: state.pageKey,
child: FruitDetailsScreen(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
// Change the opacity of the screen using a Curve based on the the animation's value
return FadeTransition(
opacity: CurveTween(curve: Curves.easeInOutCirc).animate(animation),
child: child,
);
},
);
},
),完整的示例代碼可以在 GitHub 查看:自定義轉(zhuǎn)場動畫源碼。
錯誤處理(404頁面)
go_router為MaterialApp 和CupertinoApp定義了默認的錯誤頁面,也可以通過 errorBuilder 參數(shù)自定義錯誤頁面,代碼如下。
GoRouter( /* ... */ errorBuilder: (context, state) => ErrorPage(state.error), );
類型安全路由
除了使用 URL 進行路由導航外,go_router 也通過go_router_builder插件提供了類型安全路由,這可以通過代碼生成來完成。要使用這種方式,需要在pubspec.yaml增加下面這些依賴。
dev_dependencies: go_router_builder: ^1.0.16 build_runner: ^2.3.3 build_verify: ^3.1.0
定義路由
Then define each route as a class extending GoRouteData and overriding the build method.
class HomeRoute extends GoRouteData {
const HomeRoute();
@override
Widget build(BuildContext context, GoRouterState state) => const HomeScreen();
}路由樹
路由樹基于每個頂層的路由來定義,代碼如下。
import 'package:go_router/go_router.dart';
part 'go_router.g.dart'; // name of generated file
// Define how your route tree (path and sub-routes)
@TypedGoRoute<HomeScreenRoute>(
path: '/home',
routes: [ // Add sub-routes
TypedGoRoute<SongRoute>(
path: 'song/:id',
)
]
)
// Create your route screen that extends "GoRouteData" and @override "build"
// method that return the screen for this route
@immutable
class HomeScreenRoute extends GoRouteData {
@override
Widget build(BuildContext context) {
return const HomeScreen();
}
}
@immutable
class SongRoute extends GoRouteData {
final int id;
const SongRoute({required this.id});
@override
Widget build(BuildContext context) {
return SongScreen(songId: id.toString());
}
}之后可以運行代碼生成來構(gòu)建類型安全路由。
flutter pub global activate build_runner // Optional, if you already have build_runner activated so you can skip this step flutter pub run build_runner build
導航的時候,就不再需要使用 URL的方式了,可以構(gòu)建一個GoRouteData對象然后調(diào)用go()即可。
TextButton(
onPressed: () {
const SongRoute(id: 2).go(context);
},
child: const Text('Go to song 2'),
),路由跳轉(zhuǎn)監(jiān)測
go_router還提供了一個非常有用的特性,那就是路由導航監(jiān)測NavigatorObserver。可以通過給GoRouter增加一個NavigatorObserver對象來監(jiān)聽路由行為,例如 push、pop 或路由替換(replace)。這可以通過自定義 NavigatorObserver 的子類完成。
class MyNavigatorObserver extends NavigatorObserver {
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
log('did push route');
}
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
log('did pop route');
}
}之后,在GoRouter的 observers 參數(shù)中增加自定義的MyNavigatorObserver 即可完成對所有觸發(fā)路由跳轉(zhuǎn)的行為的監(jiān)聽。
GoRouter(
...
observers: [ // Add your navigator observers
MyNavigatorObserver(),
],
...
)完整的示例代碼見 GitHub: flutter-with-go_router: A simple app to show how to work with go_router。
到此這篇關(guān)于Flutter中g(shù)o_router路由管理的使用指南的文章就介紹到這了,更多相關(guān)Flutter go_router內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android水波紋載入控件CircleWaterWaveView使用詳解
這篇文章主要為大家詳細介紹了Android水波紋載入控件CircleWaterWaveView使用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-01-01
Android如何實現(xiàn)設(shè)備的異顯功能詳解
這篇文章主要給大家介紹了關(guān)于Android如何實現(xiàn)設(shè)備的異顯功能的相關(guān)資料,這篇文章通過示例代碼介紹的非常詳細,對各位Android開發(fā)者們具有一定的參考學習價值,需要的朋友可以參考下2022-02-02
Android onMeasure與onDraw及自定義屬性使用示例
這篇文章主要介紹了Android onMeasure與onDraw及自定義屬性使用示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2023-02-02
Android WebView基礎(chǔ)應(yīng)用詳解
這篇文章主要為大家介紹了Android中WebView這一控件的基礎(chǔ)應(yīng)用,例如:播放音樂,播放視頻等,文中的示例代碼講解詳細,對于我們了解WebView很有幫助,需要的同學可以學習一下2021-12-12
Android布局ConstraintLayout代碼修改約束及輔助功能
這篇文章主要為大家介紹了Android布局ConstraintLayout代碼修改約束及輔助功能示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09

