Flutter自動路由插件auto_route使用詳解
一、簡介
在Flutter應用開發(fā)過程中,多個頁面的跳轉(zhuǎn)需要使用路由,除了官方提供的Navigator外,我們還可以使用一些第三方路由框架來實現(xiàn)頁面的管理和導航,如Fluro、Frouter等。不過,今天要給大家介紹的是另一款路由框架auto_route。
auto_route是一個設計精簡、低耦合的路由框架,支持自動生成路由代碼、動態(tài)添加路由、以及路由的參數(shù)傳遞等功能。相比其他的路由框架,auto_route的使用也更加簡潔。
二、基本使用
2.1 安裝插件
和其他Flutter插件的使用流程一樣,使用之前需要先在項目中安裝auto_route插件,安裝的的腳本如下:
dependencies: auto_route: [latest-version] dev_dependencies: auto_route_generator: [latest-version] build_runner:
2.2 定義路由表
接下來,定義一個路由表的管理類,用來同意管理應用的路由,需要使用@MaterialAutoRouter注解進行標識,如下。
@MaterialAutoRouter(
replaceInRouteName: 'Page,Route',
routes: <AutoRoute>[
AutoRoute(page: BookListPage, initial: true),
AutoRoute(page: BookDetailsPage),
],
)
class $AppRouter {}
要生成路由文件的一部分而不是獨立的 AppRouter 類,只需將 Part 指令添加到AppRouter 并擴展生成的私有路由器即可。
part 'app_router.gr.dart';
@MaterialAutoRouter(
replaceInRouteName: 'Page,Route',
routes: <AutoRoute>[
AutoRoute(page: BookListPage, initial: true),
AutoRoute(page: BookDetailsPage),
],
)
class AppRouter extends _$AppRouter{}
接下來,我們使用build_runner提供的命令即可生成路由代碼。
//自動刷新路由表 flutter packages pub run build_runner watch //生成路由代碼 flutter packages pub run build_runner build
等待命令執(zhí)行完成之后,即可在app_router.dart同級的目錄下生成一個app_route.gr.dart文件,也是我們執(zhí)行路由跳轉(zhuǎn)時需要用到的代碼。最后,我們打開main.dart入口文件,然后注冊路由文件。
class App extends StatelessWidget {
final _appRouter = AppRouter();
@override
Widget build(BuildContext context){
return MaterialApp.router(
routerDelegate: _appRouter.delegate(),
routeInformationParser: _appRouter.defaultRouteParser(),
);
}
}
2.3 生成路由
當然,auto_route還支持為每個聲明的 AutoRoute 生成一個 PageRouteInfo 對象,這些對象包含路徑信息以及從頁面的默認構(gòu)造函數(shù)中提取的強類型頁面參數(shù)。
class BookListRoute extends PageRouteInfo {
const BookListRoute() : super(name, path: '/books');
static const String name = 'BookListRoute';
}
并且,如果聲明的路由有子路由,那么 AutoRoute 會在其構(gòu)造函數(shù)中添加一個子參數(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 方法。比如,我們要打一個新的頁面,那么可以使用下面
AutoRouter.of(context).replaceAll([const LoginRoute()]); //LoginRoute為路由 //或者 AutoRouter.of(context).navigate(const BooksListRoute())
如果我們使用的是命名路由,那么可以使用navigateNamed()方法,如下。
AutoRouter.of(context).pushNamed('/books') ;
當然,很多時候,路由的跳轉(zhuǎn)還會涉及很多的參數(shù)傳遞,那么對于需要傳遞參數(shù)的路由,我們需要怎么處理呢?對于參數(shù)傳遞,我們可以在目標路由頁面使用構(gòu)造函數(shù)的方式,然后再用AutoRouter進行傳遞。
AutoRouter.of(context).pushAll([IndexRoute(login: true)]);
除了跳轉(zhuǎn),我們還可能需要處理路由彈棧的場景,對于彈棧,需要用到pop()函數(shù)。和其他的路由框架一樣,pop()默認只彈出一個,如果要彈出多個,可以使用下面的方式。
//彈出到指定的路由
context.router.popUntilRouteWithName('HomeRoute');
//彈出到最頂部
context.router.popUntilRoot();
如果要清除,或者刪除路由棧里面的內(nèi)容,可以是呀AutoRouter還提供了remove()函數(shù)。
context.router.removeLast(); context.router.removeWhere((route) => );
下面是AutoRouter常用方法的一個匯總。
context.pushRoute(const BooksListRoute());
context.replaceRoute(const BooksListRoute());
context.navigateTo(const BooksListRoute());
context.navigateNamedTo('/books');
context.navigateBack();
context.popRoute();
2.5 處理返回結(jié)果
有時候,兩個路由之間,需要獲取頁面的處理結(jié)果,并將結(jié)果返回給上一個頁面。對于這種場景,只需要在返回的時候返回結(jié)果即可,并在上一個路由使用await進行接收。
router.pop<bool>(true); var result = await router.push<bool>(LoginRoute());
三、路由導航
3.1 嵌套導航
在應用開發(fā)中,嵌套導航是一種比較常見的場景,這意味著,在一個路由頁面中嵌套另外的多個路由。

嵌套路由就像父路由的子字段一樣。在上面的示例中,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)到嵌套路由的子組件,我們使用下面的方式就可以導航到嵌套路由的子路由。
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 導航
前面我們介紹的都是棧管理,即StackRouter,遵循先進后出的邏輯。除了支持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',...),
],
));
},
);
}
}
當然,上面的代碼看起來有點復雜,所以如果我們只是實現(xiàn)Tab導航,那么可以使用下面的簡潔代碼。
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
當然,我們也可以使用 AutoTabsRouter.pageView 構(gòu)造函數(shù)來實現(xiàn)使用 PageView 的選項卡。
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 聲明式導航
聲明式導航需要與 auto_route 一起使用,只需要使用 AutoRouter.declarative 構(gòu)造函數(shù)并返回基于狀態(tài)的路由列表即可。
AutoRouter.declarative(
routes: (handler) => [
BookListRoute(),
if(_selectedBook != null)
BookDetailsRoute(id: _selectedBook.id),
],);
四、高級用法
4.1 路由控制器
事實上,每個嵌套的 AutoRouter 都有自己的路由控制器來管理其內(nèi)部的堆棧,獲得路由控制器最簡單的方法是使用上下文。在前面的示例中,我們調(diào)用的 AutoRouter.of(context) 就是用來獲得根路由控制器的。
需要說明的是,對于渲染嵌套路由的 AutoRouter 小部件,我們使用上面的方式獲取的 是小部件樹中最近的父控制器而不是根控制器,下面是一個典型的路由控制器的結(jié)構(gòu)示意圖。

從上圖中可以看出,我們可以通過調(diào)用 router.parent() 來訪問父路由控制器,對于這個通用函數(shù),在真正調(diào)用的時候,我們還需要指定類型,比如StackRouter/TabsRouter。
router.parent<StackRouter>() router.parent<TabsRouter>()
當然,如果是獲取根路由控制器,那么是不需要進行類型轉(zhuǎn)換的,因為它始終是 StackRouter。
router.root
另一方面,為了在其他地方使用這個路由控制器,可以定義一個全局的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),
)
],
);
}
}
當然,我們也可以在沒有全局key的情況下,使用下面的方式獲取路由控制器,條件是這個路由已經(jīng)啟動,這個有點類似于Java的反射機制。
context.innerRouterOf<StackRouter>(UserRoute.name) context.innerRouterOf<TabsRouter>(UserRoute.name)
4.2 Paths
在 AutoRoute 中,使用路徑是可選的,因為 PageRouteInfo 對象是按名稱匹配的,除非使用根委托中的 initialDeepLink、pushNamed、replaceNamed和navigateNamed 等方法。
如果我們不指定路徑,系統(tǒng)將自動生成路徑,例如BookListPage 將“book-list-page”作為路徑,如果初始 arg 設置為 true,則路徑將為“/”。在Flutter開發(fā)中,當頁面層級比較深時,就可以使用paths方式。
AutoRoute(path: '/books', page: BookListPage),
4.2.1 Path Parameters
當然,我們還可以在paths中添加參數(shù)。
AutoRoute(path: '/books/:id', page: BookDetailsPage),
然后,我們只需要在目標路由使用 @PathParam('optional-alias') 方式即可獲取傳遞的參數(shù),比如。
class BookDetailsPage extends StatelessWidget {
const BookDetailsPage({@PathParam('id') this.bookId});
final int bookId;
...
4.2.2 Inherited Path Parameters
不過,如果使用 @PathParm() 標識的構(gòu)造函數(shù)參數(shù)與路由沒有同名的路徑參數(shù)但它的父級有,那么該路徑參數(shù)將被繼承并且生成的路由不會將此作為參數(shù)。
AutoRoute( path: '/product/:id', page: ProductScreen, children: [ AutoRoute(path: 'review',page: ProductReviewScreen), ], ),
當然,我們還可以在路由頁面添加一個名為 id 的路徑參數(shù),從上面的示例中,我們知道ProductReviewScreen沒有路徑參數(shù),在這種情況下,auto_route 將檢查是否有任何祖先路徑可以提供此路徑參數(shù),如果有則會標記它作為路徑參數(shù),否則會引發(fā)錯誤。
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
當然,我們也可以使用RedirectRoute來實現(xiàn)路徑的重定向,重定向路徑時需要使用redirectTo參數(shù)指定重定后的路由,比如。
<AutoRoute> [
RedirectRoute(path: '/', redirectTo: '/books'),
AutoRoute(path: '/books', page: BookListPage),
]
當然,使用重定向時還可以跟一些參數(shù),比如。
<AutoRoute> [
RedirectRoute(path: 'books/:id', redirectTo: '/books/:id/details'),
AutoRoute(path: '/books/:id/details', page: BookDetailsPage),
]
除此之外,auto_route 還支持使用通配符來匹配無效或未定義的路徑,可以將它作為默認的路徑。
AutoRoute(path: '*', page: UnknownRoutePage) AutoRoute(path: '/profile/*', page: ProfilePage) RedirectRoute(path: '*', redirectTo: '/')
4.3 路由守護
我們可以將路由守衛(wèi)視為中間件或者攔截器,不經(jīng)過分配的守衛(wèi)無法將路由添加到堆棧中,這對于限制對某些路由的訪問是很有用,相當于在執(zhí)行路由跳轉(zhuǎn)前我們可以對路由做一些限制。
下面,我們使用 AutoRouteGuard 創(chuàng)建一個路由保護,然后在 onNavigation 方法中實現(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 對象包含可以調(diào)用的屬性,所以我們可以使用resolver.route 訪問的受保護路由,以及調(diào)用resolver.pendingRoutes 訪問掛起的路由列表。
接下來,我們將守衛(wèi)分配給我們想要保護的路線即可,使用方式如下。
AutoRoute(page: ProfileScreen, guards: [AuthGuard]);
有時候,我們希望獲取父窗口包裹的小部件的上下文提供的一些值,那么只需實現(xiàn) AutoRouteWrapper,并讓 WrapRoute(context) 方法返回小部件的子級即可。
class ProductsScreen extends StatelessWidget implements AutoRouteWrapper {
@override
Widget wrappedRoute(BuildContext context) {
return Provider(create: (ctx) => ProductsBloc(), child: this);
}
...
4.4 路由觀察者
為了方便查看路由棧的具體情況,我們可以通過擴展 AutoRouterObserver 來實現(xiàn),然后重寫里面的函數(shù)來進行查看,比如。
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自動路由插件auto_route使用詳解的詳細內(nèi)容,更多關(guān)于Flutter自動路由插件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android開發(fā)中記一個SwipeMenuListView側(cè)滑刪除錯亂的Bug
Ionic2創(chuàng)建App啟動頁左右滑動歡迎界面
Android Animation實戰(zhàn)之屏幕底部彈出PopupWindow
Android編程之菜單Menu的創(chuàng)建方法示例
android獲取圖片尺寸的兩種方式及bitmap的縮放操作

