go_router
基于 Navigation 2
的 Flutter
声明式路由, 通过使用声明式路由来降低复杂性, 适用于各种不同的目标平台(mobile、Web、desktop), 是由 Flutter
生态系统委员会选出的表现出最高质量水平的软件包(Flutter Favorites
)。
开发环境:
Microsoft Windows 10 Enterprise LTSC [Version 10.0.19044.1586], locale zh-CN
Flutter 2.10.3 • Channel Stable, Dart 2.16.1
Visual Studio Code, 64-bit edition (version 1.66.1)
OpenJDK Runtime Environment (build 11.0.11+9-b60-7590822)
Android SDK version 30.0.3, Platform android-31, build-tools 30.0.3
新建项目 执行命令,创建新项目:flutter create my_go_router_app
1 2 3 4 5 6 7 8 9 10 11 12 13 PS D:\flutter_repos> flutter create my_go_router_app Flutter assets will be downloaded from https://storage.flutter-io.cn. Make sure you trust this source ! Creating project my_go_router_app... Running "flutter pub get" in my_go_router_app... 1,770ms Wrote 96 files. All done ! In order to run your application, type : $ cd my_go_router_app $ flutter run Your application code is in my_go_router_app\lib\main.dart.
打开新创建的项目:
1 2 PS D:\flutter_repos> cd .\my_go_router_app\ PS D:\flutter_repos\my_go_router_app> code .
添加新文件 新建文件 lib/views/index_page.dart
, 编辑其内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import 'package:flutter/material.dart' ;class IndexPage extends StatelessWidget { const IndexPage({Key? key}) : super (key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Go Router' )), body: Column( children: [ ListTile( leading: const Icon(Icons.settings_outlined), title: const Text('设置' ), onTap: () {}, ), ], ), ); } }
新建文件 lib/views/movie_detail_page.dart
, 编辑其内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import 'package:flutter/material.dart' ;class MovieDetailPage extends StatelessWidget { const MovieDetailPage({Key? key, required this .id}) : super (key: key); final String id; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Movie Detail' )), body: Center( child: Text('当前电影ID: $id ' ), ), ); } }
新建文件 lib/views/settings_page.dart
, 编辑其内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 import 'package:flutter/material.dart' ;class SettingPage extends StatelessWidget { const SettingPage({Key? key}) : super (key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Settings' )), ); } }
新建文件 lib/views/search_page.dart
, 编辑其内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import 'package:flutter/material.dart' ;class SearchPage extends StatelessWidget { const SearchPage({Key? key, required this .query}) : super (key: key); final String query; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Search' )), body: Center( child: Text('查找的内容为: $query ' ), ), ); } }
添加 go_router
依赖 go_router
最新版本: https://pub.flutter-io.cn/packages/go_router
打开文件pubspec.yaml
, 在 dependencies
中添加 go_router
包:
1 2 3 4 5 6 dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2 go_router: ^3.0.7
创建路由表 新建文件 lib/utils/app_routes.dart
, 编辑其内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 import 'package:go_router/go_router.dart' ;import '../views/index_page.dart' ;import '../views/movie_detail_page.dart' ;import '../views/search_page.dart' ;import '../views/settings_page.dart' ;class AppRoutes { static const String homePath = '/' ; static const String settingPath = '/settings' ; static const String movieDetailPath = '/movie_detail' ; static const String searchPath = '/search' ; static const String homeNamed = 'home_page' ; static const String settingsNamed = 'setting_page' ; static const String movieDetailNamed = 'movie_detail_page' ; static const String searchNamed = 'search_page' ; static GoRouter router = GoRouter( initialLocation: homePath, routes: [ GoRoute( name: homeNamed, path: homePath, builder: (context, state) => const IndexPage(), ), GoRoute( name: settingsNamed, path: settingPath, builder: (context, state) => const SettingPage(), ), GoRoute( name: searchNamed, path: searchPath, builder: (context, state) { final query = state.queryParams['query' ]; return SearchPage(query: query!); }, ), GoRoute( name: movieDetailNamed, path: '$movieDetailPath /:id' , builder: (context, state) { final id = state.params['id' ]!; return MovieDetailPage(id: id); }, ), ], ); }
路由初始化 打开 main.dart
文件, 编辑内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import 'package:flutter/material.dart' ;import 'utils/app_routes.dart' ;void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super (key: key); @override Widget build(BuildContext context) { return MaterialApp.router( title: 'Flutter Demo' , theme: ThemeData( primarySwatch: Colors.blue, ), routeInformationParser: AppRoutes.router.routeInformationParser, routerDelegate: AppRoutes.router.routerDelegate, ); } }
使用路由 打开文件 lib/views/index_page.dart
, 编辑其内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 import 'package:flutter/material.dart' ;import 'package:go_router/go_router.dart' ;import 'package:my_go_router_app/utils/app_routes.dart' ;class IndexPage extends StatelessWidget { const IndexPage({Key? key}) : super (key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Go Router' )), body: Column( children: [ ListTile( leading: const Icon(Icons.settings_outlined), title: const Text('设置' ), onTap: () { GoRouter.of(context).push(AppRoutes.settingPath); }, ), ListTile( leading: const Icon(Icons.find_in_page_outlined), title: const Text('搜索_path' ), onTap: () { GoRouter.of(context).push( '${AppRoutes.searchPath} ?query=flutter' , ); }, ), ListTile( leading: const Icon(Icons.find_in_page_outlined), title: const Text('搜索_named' ), onTap: () { GoRouter.of(context).pushNamed( AppRoutes.searchNamed, queryParams: {'query' : 'abcd' }, ); }, ), ListTile( leading: const Icon(Icons.details_outlined), title: const Text('电影详情_path' ), onTap: () { GoRouter.of(context).push( '${AppRoutes.movieDetailPath} /654321' , ); }, ), ListTile( leading: const Icon(Icons.details_outlined), title: const Text('电影详情_named' ), onTap: () { GoRouter.of(context).pushNamed( AppRoutes.movieDetailNamed, params: {'id' : '123456' }, ); }, ), ], ), ); } }
路由导航 无参数的路径路由 要在页面之间导航,可以使用 GoRouter.go
或 GoRouter.push
方法:
1 2 onTap: () => GoRouter.of(context).go('/page2' )
1 2 onTap: () => GoRouter.of(context).push('/page2' )
go_router
还提供了一个使用 Dart扩展方法
的简化导航手段。
1 2 onTap: () => context.go('/page2' )
1 2 onTap: () => context.push('/page2' )
GoRouter.go
方法跳转后不能返回; GoRouter.push
方法会把当前路由压入堆栈,可以通过 GoRouter.of(context).pop()
返回前一页。
无参数的命名路由 1 GoRouter.of(context).goNamed('settings_page' );
1 GoRouter.of(context).pushNamed('settings_page' );
通过路由传递参数的方式1 参数传递方式1
, 参数格式类似URL:/search?query=flutter
1 2 3 4 5 6 7 8 9 GoRoute( name: 'search_page' , path: '/search' , builder: (context, state) { final query = state.queryParams['query' ]; return SearchPage(query: query!); }, ),
state.queryParams
接收用问号隔开的参数
参数传递方式1
的路由跳转的几种写法:
1 2 3 4 GoRouter.of(context).pushNamed('search_page' , queryParams: {'query' : 'abcd' }); GoRouter.of(context).push('/search?query=mobile' ); GoRouter.of(context).goNamed('search_page' , queryParams: {'query' : 'android' }); GoRouter.of(context).go('/search?query=flutter' );
通过路由传递参数的方式2 参数传递方式2
, 参数格式类似:/movie_detail/654342
1 2 3 4 5 6 7 8 9 10 11 GoRoute( name: 'movie_detail_page' , path: 'movie_detail/:id' , builder: (context, state) { final id = state.params['id' ]!; return MovieDetailPage(id: id); }, ),
state.params
接收 /
隔开的参数(按位置)
参数传递方式2
的路由跳转的几种写法:
1 2 3 4 GoRouter.of(context).pushNamed('movie_detail_page' , Params: {'id' : 654342 }); GoRouter.of(context).push('movie_detail/234321' ); GoRouter.of(context).goNamed('movie_detail_page' , Params: {'id' : 794341 }); GoRouter.of(context).go('/movie_detail/854785' );
错误处理 默认情况下,go_router
带有 MaterialApp
和 CupertinoApp
一个默认错误页面,如果不想使用自带的默认错误处理页面。可以通过设置 GoRouter
的 errorBuilder
参数来替换默认的错误页面。
1 2 3 4 5 6 7 class App extends StatelessWidget { ... final _router = GoRouter( ... errorBuilder: (context, state) => ErrorPage(state.error), ); }