Flutter自動(dòng)路由插件auto_route使用詳解
一、簡(jiǎn)介
在Flutter應(yīng)用開發(fā)過程中,多個(gè)頁(yè)面的跳轉(zhuǎn)需要使用路由,除了官方提供的Navigator外,我們還可以使用一些第三方路由框架來(lái)實(shí)現(xiàn)頁(yè)面的管理和導(dǎo)航,如Fluro、Frouter等。不過,今天要給大家介紹的是另一款路由框架auto_route。
auto_route是一個(gè)設(shè)計(jì)精簡(jiǎn)、低耦合的路由框架,支持自動(dòng)生成路由代碼、動(dòng)態(tài)添加路由、以及路由的參數(shù)傳遞等功能。相比其他的路由框架,auto_route的使用也更加簡(jiǎn)潔。
二、基本使用
2.1 安裝插件
和其他Flutter插件的使用流程一樣,使用之前需要先在項(xiàng)目中安裝auto_route插件,安裝的的腳本如下:
dependencies: auto_route: [latest-version] dev_dependencies: auto_route_generator: [latest-version] build_runner:
2.2 定義路由表
接下來(lái),定義一個(gè)路由表的管理類,用來(lái)同意管理應(yīng)用的路由,需要使用@MaterialAutoRouter注解進(jìn)行標(biāo)識(shí),如下。
@MaterialAutoRouter( replaceInRouteName: 'Page,Route', routes: <AutoRoute>[ AutoRoute(page: BookListPage, initial: true), AutoRoute(page: BookDetailsPage), ], ) class $AppRouter {}
要生成路由文件的一部分而不是獨(dú)立的 AppRouter 類,只需將 Part 指令添加到AppRouter 并擴(kuò)展生成的私有路由器即可。
part 'app_router.gr.dart'; @MaterialAutoRouter( replaceInRouteName: 'Page,Route', routes: <AutoRoute>[ AutoRoute(page: BookListPage, initial: true), AutoRoute(page: BookDetailsPage), ], ) class AppRouter extends _$AppRouter{}
接下來(lái),我們使用build_runner提供的命令即可生成路由代碼。
//自動(dòng)刷新路由表 flutter packages pub run build_runner watch //生成路由代碼 flutter packages pub run build_runner build
等待命令執(zhí)行完成之后,即可在app_router.dart同級(jí)的目錄下生成一個(gè)app_route.gr.dart文件,也是我們執(zhí)行路由跳轉(zhuǎn)時(shí)需要用到的代碼。最后,我們打開main.dart入口文件,然后注冊(cè)路由文件。
class App extends StatelessWidget { final _appRouter = AppRouter(); @override Widget build(BuildContext context){ return MaterialApp.router( routerDelegate: _appRouter.delegate(), routeInformationParser: _appRouter.defaultRouteParser(), ); } }
2.3 生成路由
當(dāng)然,auto_route還支持為每個(gè)聲明的 AutoRoute 生成一個(gè) PageRouteInfo 對(duì)象,這些對(duì)象包含路徑信息以及從頁(yè)面的默認(rèn)構(gòu)造函數(shù)中提取的強(qiáng)類型頁(yè)面參數(shù)。
class BookListRoute extends PageRouteInfo { const BookListRoute() : super(name, path: '/books'); static const String name = 'BookListRoute'; }
并且,如果聲明的路由有子路由,那么 AutoRoute 會(huì)在其構(gòu)造函數(shù)中添加一個(gè)子參數(shù),如下。
class UserRoute extends PageRouteInfo { UserRoute({List<PagerouteInfo> children}) : super( name, path: '/user/:id', initialChildren: children); static const String name = 'UserRoute'; }
2.4 路由跳轉(zhuǎn)
和其他的路由框架一樣,AutoRouter 也提供常見的 push、pop 和 remove 方法。比如,我們要打一個(gè)新的頁(yè)面,那么可以使用下面
AutoRouter.of(context).replaceAll([const LoginRoute()]); //LoginRoute為路由 //或者 AutoRouter.of(context).navigate(const BooksListRoute())
如果我們使用的是命名路由,那么可以使用navigateNamed()方法,如下。
AutoRouter.of(context).pushNamed('/books') ;
當(dāng)然,很多時(shí)候,路由的跳轉(zhuǎn)還會(huì)涉及很多的參數(shù)傳遞,那么對(duì)于需要傳遞參數(shù)的路由,我們需要怎么處理呢?對(duì)于參數(shù)傳遞,我們可以在目標(biāo)路由頁(yè)面使用構(gòu)造函數(shù)的方式,然后再用AutoRouter進(jìn)行傳遞。
AutoRouter.of(context).pushAll([IndexRoute(login: true)]);
除了跳轉(zhuǎn),我們還可能需要處理路由彈棧的場(chǎng)景,對(duì)于彈棧,需要用到pop()函數(shù)。和其他的路由框架一樣,pop()默認(rèn)只彈出一個(gè),如果要彈出多個(gè),可以使用下面的方式。
//彈出到指定的路由 context.router.popUntilRouteWithName('HomeRoute'); //彈出到最頂部 context.router.popUntilRoot();
如果要清除,或者刪除路由棧里面的內(nèi)容,可以是呀AutoRouter還提供了remove()函數(shù)。
context.router.removeLast(); context.router.removeWhere((route) => );
下面是AutoRouter常用方法的一個(gè)匯總。
context.pushRoute(const BooksListRoute()); context.replaceRoute(const BooksListRoute()); context.navigateTo(const BooksListRoute()); context.navigateNamedTo('/books'); context.navigateBack(); context.popRoute();
2.5 處理返回結(jié)果
有時(shí)候,兩個(gè)路由之間,需要獲取頁(yè)面的處理結(jié)果,并將結(jié)果返回給上一個(gè)頁(yè)面。對(duì)于這種場(chǎng)景,只需要在返回的時(shí)候返回結(jié)果即可,并在上一個(gè)路由使用await進(jìn)行接收。
router.pop<bool>(true); var result = await router.push<bool>(LoginRoute());
三、路由導(dǎo)航
3.1 嵌套導(dǎo)航
在應(yīng)用開發(fā)中,嵌套導(dǎo)航是一種比較常見的場(chǎng)景,這意味著,在一個(gè)路由頁(yè)面中嵌套另外的多個(gè)路由。
嵌套路由就像父路由的子字段一樣。在上面的示例中,UsersPage、PostsPage 和SettingsPage就是DashboardPage的子路由,所以它們的定義如下。
@MaterialAutoRouter( replaceInRouteName: 'Page,Route', routes: <AutoRoute>[ AutoRoute( path: '/dashboard', page: DashboardPage, children: [ AutoRoute(path: 'users', page: UsersPage), AutoRoute(path: 'posts', page: PostsPage), AutoRoute(path: 'settings', page: SettingsPage), ], ), AutoRoute(path: '/login', page: LoginPage) ], ) class $AppRouter {}
要完成嵌套路由渲染和構(gòu)建,我們需要在嵌套路由的最外層使用AutoRouter 的小部件,如下。
class DashboardPage extends StatelessWidget { @override Widget build(BuildContext context) { return Row( children: [ Column( children: [ NavLink(label: 'Users', destination: const UsersRoute()), NavLink(label: 'Posts', destination: const PostsRoute()), NavLink(label: 'Settings', destination: const SettingsRoute()), ], ), Expanded( // nested routes will be rendered here child: AutoRouter(), ) ], ); } }
如果我們需要跳轉(zhuǎn)到嵌套路由的子組件,我們使用下面的方式就可以導(dǎo)航到嵌套路由的子路由。
AutoRoute( path: '/dashboard', page: DashboardPage, children: [ AutoRoute(path: '', page: UsersPage), //The same thing can be done using the initial flag //AutoRoute(page: UsersPage,initial: true), AutoRoute(path: 'posts', page: PostsPage), ], ),
3.2 Tab 導(dǎo)航
前面我們介紹的都是棧管理,即StackRouter,遵循先進(jìn)后出的邏輯。除了支持StackRouter,auto_route還支持Tab Navigation,下面是示例代碼。
class DashboardPage extends StatelessWidget { @override Widget build(BuildContext context) { return AutoTabsRouter( routes: const [ UsersRoute(), PostsRoute(), SettingsRoute(), ], builder: (context, child, animation) { final tabsRouter = AutoTabsRouter.of(context); return Scaffold( body: FadeTransition( opacity: animation, child: child, ), bottomNavigationBar: BottomNavigationBar( currentIndex: tabsRouter.activeIndex, onTap: (index) { tabsRouter.setActiveIndex(index); }, items: [ BottomNavigationBarItem(label: 'Users',...), BottomNavigationBarItem(label: 'Posts',...), BottomNavigationBarItem(label: 'Settings',...), ], )); }, ); } }
當(dāng)然,上面的代碼看起來(lái)有點(diǎn)復(fù)雜,所以如果我們只是實(shí)現(xiàn)Tab導(dǎo)航,那么可以使用下面的簡(jiǎn)潔代碼。
class DashboardPage extends StatelessWidget { @override Widget build(context) { @override Widget build(context) { return AutoTabsScaffold( routes: const [ UsersRoute(), PostsRoute(), SettingsRoute(), ], bottomNavigationBuilder: (_,tabsRouter) { return BottomNavigationBar( currentIndex: tabsRouter.activeIndex, onTap: tabsRouter.setActiveIndex items: [ BottomNavigationBarItem(label: 'Users',...), BottomNavigationBarItem(label: 'Posts',...), BottomNavigationBarItem(label: 'Settings',...), ], )), } ); }
3.3 PageView
當(dāng)然,我們也可以使用 AutoTabsRouter.pageView 構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)使用 PageView 的選項(xiàng)卡。
AutoTabsRouter.pageView( routes: [ BooksTab(), ProfileTab(), SettingsTab(), ], builder: (context, child, _) { return Scaffold( appBar: AppBar( title: Text(context.topRoute.name), leading: AutoLeadingButton()), body: child, bottomNavigationBar: BottomNavigationBar( currentIndex: tabsRouter.activeIndex, onTap: tabsRouter.setActiveIndex items: [ BottomNavigationBarItem(label: 'Books',...), BottomNavigationBarItem(label: 'Profile',...), BottomNavigationBarItem(label: 'Settings',...), ], ), ), ); }, );
3.4 聲明式導(dǎo)航
聲明式導(dǎo)航需要與 auto_route 一起使用,只需要使用 AutoRouter.declarative 構(gòu)造函數(shù)并返回基于狀態(tài)的路由列表即可。
AutoRouter.declarative( routes: (handler) => [ BookListRoute(), if(_selectedBook != null) BookDetailsRoute(id: _selectedBook.id), ],);
四、高級(jí)用法
4.1 路由控制器
事實(shí)上,每個(gè)嵌套的 AutoRouter 都有自己的路由控制器來(lái)管理其內(nèi)部的堆棧,獲得路由控制器最簡(jiǎn)單的方法是使用上下文。在前面的示例中,我們調(diào)用的 AutoRouter.of(context) 就是用來(lái)獲得根路由控制器的。
需要說明的是,對(duì)于渲染嵌套路由的 AutoRouter 小部件,我們使用上面的方式獲取的 是小部件樹中最近的父控制器而不是根控制器,下面是一個(gè)典型的路由控制器的結(jié)構(gòu)示意圖。
從上圖中可以看出,我們可以通過調(diào)用 router.parent() 來(lái)訪問父路由控制器,對(duì)于這個(gè)通用函數(shù),在真正調(diào)用的時(shí)候,我們還需要指定類型,比如StackRouter/TabsRouter。
router.parent<StackRouter>() router.parent<TabsRouter>()
當(dāng)然,如果是獲取根路由控制器,那么是不需要進(jìn)行類型轉(zhuǎn)換的,因?yàn)樗冀K是 StackRouter。
router.root
另一方面,為了在其他地方使用這個(gè)路由控制器,可以定義一個(gè)全局的key,比如。
class DashboardPage extends StatefulWidget { @override _DashboardPageState createState() => _DashboardPageState(); } class _DashboardPageState extends State<DashboardPage> { final _innerRouterKey = GlobalKey<AutoRouterState>(); @override Widget build(BuildContext context) { return Row( children: [ Column( children: [ NavLink(label: 'Users', onTap:(){ final router = _innerRouterKey.currentState?.controller; router?.push(const UsersRoute()); } ), ... ], ), Expanded( child: AutoRouter(key: _innerRouterKey), ) ], ); } }
當(dāng)然,我們也可以在沒有全局key的情況下,使用下面的方式獲取路由控制器,條件是這個(gè)路由已經(jīng)啟動(dòng),這個(gè)有點(diǎn)類似于Java的反射機(jī)制。
context.innerRouterOf<StackRouter>(UserRoute.name) context.innerRouterOf<TabsRouter>(UserRoute.name)
4.2 Paths
在 AutoRoute 中,使用路徑是可選的,因?yàn)?PageRouteInfo 對(duì)象是按名稱匹配的,除非使用根委托中的 initialDeepLink、pushNamed、replaceNamed和navigateNamed 等方法。
如果我們不指定路徑,系統(tǒng)將自動(dòng)生成路徑,例如BookListPage 將“book-list-page”作為路徑,如果初始 arg 設(shè)置為 true,則路徑將為“/”。在Flutter開發(fā)中,當(dāng)頁(yè)面層級(jí)比較深時(shí),就可以使用paths方式。
AutoRoute(path: '/books', page: BookListPage),
4.2.1 Path Parameters
當(dāng)然,我們還可以在paths中添加參數(shù)。
AutoRoute(path: '/books/:id', page: BookDetailsPage),
然后,我們只需要在目標(biāo)路由使用 @PathParam('optional-alias') 方式即可獲取傳遞的參數(shù),比如。
class BookDetailsPage extends StatelessWidget { const BookDetailsPage({@PathParam('id') this.bookId}); final int bookId; ...
4.2.2 Inherited Path Parameters
不過,如果使用 @PathParm() 標(biāo)識(shí)的構(gòu)造函數(shù)參數(shù)與路由沒有同名的路徑參數(shù)但它的父級(jí)有,那么該路徑參數(shù)將被繼承并且生成的路由不會(huì)將此作為參數(shù)。
AutoRoute( path: '/product/:id', page: ProductScreen, children: [ AutoRoute(path: 'review',page: ProductReviewScreen), ], ),
當(dāng)然,我們還可以在路由頁(yè)面添加一個(gè)名為 id 的路徑參數(shù),從上面的示例中,我們知道ProductReviewScreen沒有路徑參數(shù),在這種情況下,auto_route 將檢查是否有任何祖先路徑可以提供此路徑參數(shù),如果有則會(huì)標(biāo)記它作為路徑參數(shù),否則會(huì)引發(fā)錯(cuò)誤。
class ProductReviewScreen extends StatelessWidget { const ProductReviewScreen({super.key, @pathParam required String id}); }
4.2.3 Query Parameters
和前面的查詢參數(shù)的方式相同,只需使用 @QueryParam('optional-alias') 注解構(gòu)造函數(shù)參數(shù)即可獲取參數(shù)的值。
RouteData.of(context).pathParams; context.routeData.queryParams
如果參數(shù)名稱與路徑/查詢參數(shù)相同,則可以使用 const @pathParam 或者@queryParam 并且不需要傳遞 slug/別名,比如。
class BookDetailsPage extends StatelessWidget { const BookDetailsPage({@pathParam this.id}); final int id; ...
4.2.4 Redirecting Paths
當(dāng)然,我們也可以使用RedirectRoute來(lái)實(shí)現(xiàn)路徑的重定向,重定向路徑時(shí)需要使用redirectTo參數(shù)指定重定后的路由,比如。
<AutoRoute> [ RedirectRoute(path: '/', redirectTo: '/books'), AutoRoute(path: '/books', page: BookListPage), ]
當(dāng)然,使用重定向時(shí)還可以跟一些參數(shù),比如。
<AutoRoute> [ RedirectRoute(path: 'books/:id', redirectTo: '/books/:id/details'), AutoRoute(path: '/books/:id/details', page: BookDetailsPage), ]
除此之外,auto_route 還支持使用通配符來(lái)匹配無(wú)效或未定義的路徑,可以將它作為默認(rèn)的路徑。
AutoRoute(path: '*', page: UnknownRoutePage) AutoRoute(path: '/profile/*', page: ProfilePage) RedirectRoute(path: '*', redirectTo: '/')
4.3 路由守護(hù)
我們可以將路由守衛(wèi)視為中間件或者攔截器,不經(jīng)過分配的守衛(wèi)無(wú)法將路由添加到堆棧中,這對(duì)于限制對(duì)某些路由的訪問是很有用,相當(dāng)于在執(zhí)行路由跳轉(zhuǎn)前我們可以對(duì)路由做一些限制。
下面,我們使用 AutoRouteGuard 創(chuàng)建一個(gè)路由保護(hù),然后在 onNavigation 方法中實(shí)現(xiàn)我們的路由邏輯。
class AuthGuard extends AutoRouteGuard { @override void onNavigation(NavigationResolver resolver, StackRouter router) { //觸發(fā)條件 if(authenitcated){ resolver.next(true); }else{ router.push(LoginRoute(onResult: (success){ resolver.next(success); })); } } }
在onNavigation方法中,NavigationResolver 對(duì)象包含可以調(diào)用的屬性,所以我們可以使用resolver.route 訪問的受保護(hù)路由,以及調(diào)用resolver.pendingRoutes 訪問掛起的路由列表。
接下來(lái),我們將守衛(wèi)分配給我們想要保護(hù)的路線即可,使用方式如下。
AutoRoute(page: ProfileScreen, guards: [AuthGuard]);
有時(shí)候,我們希望獲取父窗口包裹的小部件的上下文提供的一些值,那么只需實(shí)現(xiàn) AutoRouteWrapper,并讓 WrapRoute(context) 方法返回小部件的子級(jí)即可。
class ProductsScreen extends StatelessWidget implements AutoRouteWrapper { @override Widget wrappedRoute(BuildContext context) { return Provider(create: (ctx) => ProductsBloc(), child: this); } ...
4.4 路由觀察者
為了方便查看路由棧的具體情況,我們可以通過擴(kuò)展 AutoRouterObserver 來(lái)實(shí)現(xiàn),然后重寫里面的函數(shù)來(lái)進(jìn)行查看,比如。
class MyObserver extends AutoRouterObserver { @override void didPush(Route route, Route? previousRoute) { print('New route pushed: ${route.settings.name}'); } @override void didInitTabRoute(TabPageRoute route, TabPageRoute? previousRoute) { print('Tab route visited: ${route.name}'); } @override void didChangeTabRoute(TabPageRoute route, TabPageRoute previousRoute) { print('Tab route re-visited: ${route.name}'); } }
然后,我們將觀察者傳遞給根委托 AutoRouterDelegate。
return MaterialApp.router( routerDelegate: AutoRouterDelegate( _appRouter, navigatorObservers: () => [MyObserver()], ), routeInformationParser: _appRouter.defaultRouteParser(), );
以上就是Flutter自動(dòng)路由插件auto_route使用詳解的詳細(xì)內(nèi)容,更多關(guān)于Flutter自動(dòng)路由插件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章

Android開發(fā)中記一個(gè)SwipeMenuListView側(cè)滑刪除錯(cuò)亂的Bug

Ionic2創(chuàng)建App啟動(dòng)頁(yè)左右滑動(dòng)歡迎界面

Android Animation實(shí)戰(zhàn)之屏幕底部彈出PopupWindow

Android系統(tǒng)對(duì)話框使用詳解(最詳細(xì))

Android編程之菜單Menu的創(chuàng)建方法示例

android獲取圖片尺寸的兩種方式及bitmap的縮放操作