Flutter中g(shù)o_router路由管理的使用指南
前言
go_router
是一個(gè) Flutter 的第三方路由插件,相比 Flutter 自帶的路由,go_router
更加靈活,而且簡(jiǎn)單易用。在 App 應(yīng)用中,如果你想自己控制路由的定義和管理方式,那么它將十分有用。同時(shí),對(duì)于 Web 應(yīng)用來(lái)說(shuō),go_router
也提供了很好的支持。 使用 go_router
后,你可以定義 URL 的格式,使用 URL 跳轉(zhuǎn),處理深度鏈接以及其他一系列的導(dǎo)航相關(guān)的應(yīng)用場(chǎng)景。
GoRouter 特性
GoRouter
針對(duì)頁(yè)面導(dǎo)航提供了下面這些特性:
- 使用模板語(yǔ)法解析路由路徑和路由查詢(
query
)參數(shù); - 支持單個(gè)目標(biāo)路由展示多個(gè)頁(yè)面(子路由);
- 重定向:可以基于應(yīng)用狀態(tài)跳轉(zhuǎn)到不同的URL,比如用戶沒(méi)有登錄時(shí)跳轉(zhuǎn)到登錄頁(yè);
- 使用
StatefulShellRoute
可以支持嵌套的 Tab 導(dǎo)航; - 同時(shí)支持
Material
風(fēng)格和Cupertino
風(fēng)格應(yīng)用; - 兼容
Navigator
API 。
添加插件
當(dāng)前最新版本的 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(), ), ], );
然后,我們就可以通過(guò)MaterialApp.router
或CupertinoApp.router
構(gòu)造函數(shù)來(lái)使用 GoRouter
,并且將 routerConfig
參數(shù)設(shè)置為我們前面定義的 GoRouter
配置對(duì)象。
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp.router( routerConfig: _router, ); } }
接下來(lái)就可以愉快地玩耍 GoRouter
了。
路由參數(shù)
GoRouter
的每一個(gè)路由都通過(guò) GoRoute
對(duì)象來(lái)配置,我們可以在構(gòu)建 GoRoute
對(duì)象時(shí)來(lái)配置路由參數(shù)。路由參數(shù)典型的就是路徑參數(shù),比如 /path/:{路徑參數(shù)}
,這個(gè)時(shí)候 GoRoute
的路徑參數(shù)和很多 Web 框架的路由是一樣的,通過(guò)一個(gè)英文冒號(hào)加參數(shù)名稱就可以配置,之后我們可以在回調(diào)方法中通過(guò) GoRouterState
對(duì)象獲取路徑參數(shù),這個(gè)參數(shù)就可以傳遞到路由跳轉(zhuǎn)目的頁(yè)面。
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); }, ),
添加子路由
路由匹配后可以支持多個(gè)頁(yè)面(即子路由),當(dāng)一個(gè)新的頁(yè)面在舊的頁(yè)面之上展示時(shí),這個(gè)時(shí)候的效果和調(diào)用 push
方法是一樣的,。如果頁(yè)面提供了 AppBar 組件的話,那么會(huì)自動(dòng)增加返回按鈕。 要使用子路由,我們只需要在上級(jí)路由中增加對(duì)應(yīng)的下級(jí)路由即可,代碼如下。
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(); }, ), ], )
頁(yè)面導(dǎo)航
GoRouter 提供了多種方式跳轉(zhuǎn)到目的頁(yè)面,比如使用context.go()
跳轉(zhuǎn)到指定的 URL 地址。
build(BuildContext context) { return TextButton( onPressed: () => context.go('/fruits/fruit-detail'), ); }
也可以使用路由的名稱進(jìn)行跳轉(zhuǎn),這個(gè)時(shí)候調(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 類來(lái)構(gòu)建路由路徑。
context.go( Uri( path: '/fruit-detail', queryParameters: {'id': '10'}, ).toString(), );
如果要從當(dāng)前頁(yè)面返回的話,調(diào)用context.pop()
即可。
嵌套導(dǎo)航
有些應(yīng)用在同一個(gè)頁(yè)面展示多個(gè)子頁(yè)面,例如 BottomNavigationBar
在進(jìn)行導(dǎo)航的時(shí)候就可以一直保留在屏幕底部。這種其實(shí)是嵌套導(dǎo)航,在 GoRouter
里,可以通過(guò)StatefulShellRoute
來(lái)實(shí)現(xiàn)。 StatefulShellRoute
不直接使用根導(dǎo)航(root Navigator),而是通過(guò)不同的導(dǎo)航的子路由來(lái)實(shí)現(xiàn)嵌套導(dǎo)航。對(duì)于每一個(gè)導(dǎo)航分支,都會(huì)創(chuàng)建各自獨(dú)立的導(dǎo)航,相當(dāng)于是一個(gè)并行導(dǎo)航樹,從而實(shí)現(xiàn)了有狀態(tài)的嵌套導(dǎo)航。 當(dāng)我們使用BottomNavigationBar
的時(shí)候,就可以為很方便地為每一個(gè) Tab 配置一個(gè)持久的導(dǎo)航狀態(tài)。 StatefulShellRoute
通過(guò)指定一個(gè)StatefulShellBranch
類型的列表來(lái)完成,列表每一個(gè)元素代表路由樹的一個(gè)獨(dú)立的有狀態(tài)的分支。StatefulShellBranch
為每個(gè)分支提供了根路由和 Navigator
key(GlobalKey
),并提供了可選的初始默認(rèn)路由地址。 我們來(lái)看看具體怎么實(shí)現(xiàn)。 首先創(chuàng)建我們的 GoRouter
對(duì)象,這個(gè)時(shí)候我們需要添加StatefulShellRoute.indexedStack
到路由中,這個(gè)類負(fù)責(zé)創(chuàng)建嵌套路由。StatefulShellRoute.indexedStack() 實(shí)際上是使用了 IndexedStack
創(chuàng)建了一個(gè)StatefulShellRoute
。 這個(gè)構(gòu)造函數(shù)使用IndexedStack
來(lái)管理每個(gè)分支導(dǎo)航的頁(yè)面,示例代碼如下:
// 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(), ), ]) ], ), ], );
在上面的代碼中,我們?cè)诼酚芍屑尤肓?code>StatefulShellRoute.indexedStack(),由它負(fù)責(zé)創(chuàng)建路由分支以及返回一個(gè)自定義的導(dǎo)航殼,這里是BottomNavigationBar
。
- 在 builder 參數(shù)中, 我們返回導(dǎo)航用的殼,一個(gè)簡(jiǎn)單的帶有
BottomNavigationBar
的Scaffold
,這里需要記得將navigationShell
傳給頁(yè)面,因?yàn)槲覀冃枰盟鼇?lái)導(dǎo)航到其他分支,例如從Home到 Shope。 - 在路由分支數(shù)組
branches
中,我們提供了一個(gè)StatefulShellBranch
的數(shù)組。這里只需要給第一個(gè)元素的navigatorKey
提供之前創(chuàng)建的全局的_sectionNavigatorKey
。其他分支則使用默認(rèn)的 key。同時(shí),為每個(gè)分支提供了一個(gè)RouteBase
列表,該列表是對(duì)應(yīng)分支的路由。
下面是我們定義的帶有BottomNavigationBar
的自定義導(dǎo)航殼的代碼。
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, ); } }
在上面的代碼中,實(shí)際上就是構(gòu)建帶有BottomNavigationBar
的Scaffold
然后 body 是從路由里獲取的navigationShell
. 路由分支及頁(yè)面的切換通過(guò)_onTap(index)
實(shí)現(xiàn),當(dāng)點(diǎn)擊某個(gè) Tab 時(shí),就使用navigationShell.goBranch(index)
來(lái)完成切換動(dòng)作。完整代碼:flutter-go_router-with-nested-tab-navigation。
路由守衛(wèi)(Guards)
有些路由地址需要守衛(wèi),例如對(duì)于沒(méi)有登錄的用戶,有些頁(yè)面就無(wú)法訪問(wèn)。GoRouter
可以設(shè)置全局的重定向路由。最常見的一個(gè)場(chǎng)景就是對(duì)于沒(méi)有登錄的用戶,跳轉(zhuǎn)到/login
登錄頁(yè)面。 在 GoRouter 中,可以通過(guò)redirect
參數(shù)配置重定向,這是一個(gè)GoRouterRedirect
的回調(diào)方法。如果要基于應(yīng)用狀態(tài)更改跳轉(zhuǎn)都只,那么既可以在GoRouter
或GoRoute
的構(gòu)造方法中增加redirect
參數(shù)。其中 GoRoute
是只針對(duì)當(dāng)前路由進(jìn)行跳轉(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 } }, ...
也可以指定一個(gè)redirectLimit
參數(shù)來(lái)限制最大的跳轉(zhuǎn)次數(shù),這個(gè)值默認(rèn)是5。如果超過(guò)了跳轉(zhuǎn)次數(shù),則會(huì)顯示一個(gè)錯(cuò)誤頁(yè)面。
轉(zhuǎn)場(chǎng)動(dòng)畫
GoRouter
支持為每個(gè) GoRoute
自定義轉(zhuǎn)場(chǎng)動(dòng)畫,這可以通過(guò)GoRoute
的構(gòu)造函數(shù)的pageBuilder
參數(shù)來(lái)完成,下面是示例代碼。
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)場(chǎng)動(dòng)畫源碼。
錯(cuò)誤處理(404頁(yè)面)
go_router
為MaterialApp
和CupertinoApp
定義了默認(rèn)的錯(cuò)誤頁(yè)面,也可以通過(guò) errorBuilder 參數(shù)自定義錯(cuò)誤頁(yè)面,代碼如下。
GoRouter( /* ... */ errorBuilder: (context, state) => ErrorPage(state.error), );
類型安全路由
除了使用 URL 進(jìn)行路由導(dǎo)航外,go_router 也通過(guò)go_router_builder
插件提供了類型安全路由,這可以通過(guò)代碼生成來(lái)完成。要使用這種方式,需要在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(); }
路由樹
路由樹基于每個(gè)頂層的路由來(lái)定義,代碼如下。
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()); } }
之后可以運(yùn)行代碼生成來(lái)構(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
導(dǎo)航的時(shí)候,就不再需要使用 URL的方式了,可以構(gòu)建一個(gè)GoRouteData對(duì)象然后調(diào)用go()
即可。
TextButton( onPressed: () { const SongRoute(id: 2).go(context); }, child: const Text('Go to song 2'), ),
路由跳轉(zhuǎn)監(jiān)測(cè)
go_router還提供了一個(gè)非常有用的特性,那就是路由導(dǎo)航監(jiān)測(cè)NavigatorObserver
??梢酝ㄟ^(guò)給GoRouter增加一個(gè)NavigatorObserver
對(duì)象來(lái)監(jiān)聽路由行為,例如 push
、pop
或路由替換(replace
)。這可以通過(guò)自定義 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
即可完成對(duì)所有觸發(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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android水波紋載入控件CircleWaterWaveView使用詳解
這篇文章主要為大家詳細(xì)介紹了Android水波紋載入控件CircleWaterWaveView使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01Android Studio設(shè)置繪制布局時(shí)的視圖
這篇文章介紹了Android Studio設(shè)置繪制布局時(shí)視圖的方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-11-11Android如何實(shí)現(xiàn)設(shè)備的異顯功能詳解
這篇文章主要給大家介紹了關(guān)于Android如何實(shí)現(xiàn)設(shè)備的異顯功能的相關(guān)資料,這篇文章通過(guò)示例代碼介紹的非常詳細(xì),對(duì)各位Android開發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-02-02Android實(shí)現(xiàn)懸浮窗全系統(tǒng)版本
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)懸浮窗全系統(tǒng)版本,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11Android LayerDrawable使用實(shí)例
這篇文章主要介紹了Android LayerDrawable使用實(shí)例,本文講解了LayerDrawable的作用、LayerDrawable的原理、LayerDrawableLayerDrawable的使用實(shí)例等,需要的朋友可以參考下2015-06-06Android中毛玻璃效果的兩種實(shí)現(xiàn)代碼
這篇文章主要介紹了Android中毛玻璃效果的兩種實(shí)現(xiàn)代碼,第一種是使用JAVA算法FastBlur實(shí)現(xiàn),第二種是使用Android自帶類RenderScript 實(shí)現(xiàn),本文通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友參考下吧2024-08-08Android onMeasure與onDraw及自定義屬性使用示例
這篇文章主要介紹了Android onMeasure與onDraw及自定義屬性使用示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-02-02Android WebView基礎(chǔ)應(yīng)用詳解
這篇文章主要為大家介紹了Android中WebView這一控件的基礎(chǔ)應(yīng)用,例如:播放音樂(lè),播放視頻等,文中的示例代碼講解詳細(xì),對(duì)于我們了解WebView很有幫助,需要的同學(xué)可以學(xué)習(xí)一下2021-12-12Android布局ConstraintLayout代碼修改約束及輔助功能
這篇文章主要為大家介紹了Android布局ConstraintLayout代碼修改約束及輔助功能示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09