GoRouter 与 AutoRouter 的使用和对比
前言
在现代移动应用开发中,路由管理是一个至关重要的部分。一个良好的路由框架不仅能提高代码的可维护性,还能提升开发效率和用户体验。Flutter 作为一个跨平台框架,提供了多种路由解决方案,其中原生的 Navigator 和 Route 已经能够满足基本需求,但在实际项目中,往往需要更为强大和灵活的路由管理功能。
最近在重构一个新的框架时,我在路由选项上遇到了一些困惑。Flutter 的生态系统中有许多优秀的路由框架,其中 go_router 和 auto_route 是两款备受欢迎且使用频率较高的路由管理工具。为了选择最适合项目需求的路由框架,我决定对这两者进行深入比较和分析。
本文将详细记录 go_router 和 auto_route 的相关特点、使用方法,以及它们在不同场景中的优劣势,希望能为遇到相似问题的开发者提供一些参考和帮助。通过这篇文章,你将能够更好地理解这两种路由框架,并做出更明智的选择。
一、 go_router 与 auto_route 的异同点。
go_router
和 auto_route
都是建立在 Flutter Navigator 2.0 API 之上的路由库,它们的目标是简化 Flutter 应用中的路由和导航管理。它们都提供了声明式路由的方式和更好的深层链接支持。下面是两者的一些特点介绍与比较。
1.1 go_router 的特点
-
声明式 API :
go_router
使用声明式 API,可以很容易地定义路由和它们的行为。 -
简化的路由定义: 它允许你通过简单的配置定义路由,使得路由表管理更加直观和集中。
-
路由守卫 :
go_router
支持路由守卫(route guards),这允许开发者在路由跳转前执行权限检查或其他逻辑。 -
重定向: 支持重定向,可以根据业务逻辑动态改变目标路由。
-
易于理解 : 相对于 Navigator 2.0 的底层 API,
go_router
的 API 更易于理解和使用。
1.2 auto_route 的特点
-
代码生成 :
auto_route
通过代码生成来处理路由的创建,这使得路由表的维护更加类型安全和易于重构。 -
强类型导航 : 因为其生成的代码,
auto_route
提供强类型的导航方法,降低了因为类型错误而产生的 bug。 -
嵌套路由 :
auto_route
支持嵌套路由,非常适合构建复杂的应用界面结构。 -
页面包装器: 支持自定义页面包装器(wrappers),可以用于处理例如主题、语言等上下文相关的配置。
-
灵活性: 提供更多的灵活性来定义和管理路由,但同时带来的是更复杂的配置。
1.3 异同点
相同点:
- 都是为了简化 Flutter Navigator 2.0 的路由管理。
- 都支持深层链接处理和复杂的路由场景。
- 都提供了路由守卫和重定向的功能。
不同点:
go_router
重在简化和去除样板代码,使用声明式 API。auto_route
通过代码生成来创建强类型的路由,以此实现更严格的类型检查和路由管理。auto_route
需要额外的步骤来生成代码,而go_router
不需要。auto_route
在嵌套路由和类型安全方面可能有更多的优势,而go_router
更加侧重于简单和易用性。
二、 go_router 的使用
go_router 的使用不需要使用注解来配合 build_runner 自动化生成,我们手动的定义即可。
2.1 go_router 的集成与简单使用
在开始使用 go_router 之前,我们需要将其添加到项目的依赖中,并进行基本的集成配置。以下是具体步骤:
添加依赖:
在 pubspec.yaml 文件中添加 go_router 依赖:
yaml
yaml
dependencies:
go_router: ^14.3.0
创建一个 GoRouter 实例并配置路由:
php
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
final GoRouter _router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => HomePage(),
),
GoRoute(
path: '/details/:id',
builder: (context, state) => DetailsPage(id: state.params['id']!),
),
],
);
runApp(MyApp(router: _router));
}
class MyApp extends StatelessWidget {
final GoRouter router;
MyApp({required this.router});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerDelegate: router.routerDelegate,
routeInformationParser: router.routeInformationParser,
routeInformationProvider: router.routeInformationProvider,
);
}
}
创建 HomePage 和 DetailsPage 作为示例页面:
scala
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home')),
body: Center(
child: ElevatedButton(
onPressed: () {
context.go('/details/1');
},
child: Text('Go to Details'),
),
),
);
}
}
class DetailsPage extends StatelessWidget {
final String id;
DetailsPage({required this.id});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Details $id')),
body: Center(
child: Text('Details Page: $id'),
),
);
}
}
通过以上步骤,我们完成了 go_router 的基本集成和简单使用。
2.2 go_router 的跳转与参数传递
最新版本中,go_router 的跳转和参数传递方法基本保持不变,但增加了一些新特性和改进。
- 基本跳转:
使用 context.go 进行页面跳转:
go
context.go('/details/1');
- 通过路径参数传递数据:
在路由配置中使用路径参数,并在目标页面中获取:
less
GoRoute(
path: '/details/:id',
builder: (context, state) => DetailsPage(id: state.params['id']!),
);
- 通过查询参数传递数据:
使用 queryParameters 传递查询参数:
go
context.go('/details/1?name=John');
在目标页面中获取查询参数:
ini
final name = state.queryParams['name'];
- 通过 extra 传递复杂数据:
使用 extra 传递复杂对象:
css
context.go('/details/1', extra: {'key': 'value'});
在目标页面中获取 extra:
ini
final extra = state.extra as Map<String, dynamic>?;
final value = extra?['key'];
示例:
我们先定义一个对象,然后通过 extra 的方式传递
dart
class Item {
final int id;
final String name;
Item({required this.id, required this.name});
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
};
factory Item.fromJson(Map<String, dynamic> json) => Item(
id: json['id'],
name: json['name'],
);
}
在 GoRouter 配置中,使用 extra 参数传递复杂数据:
less
final GoRouter _router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => HomePage(),
),
GoRoute(
path: '/details',
builder: (context, state) {
final item = state.extra as Item?;
return DetailsPage(item: item!);
},
),
],
);
在目标页面 DetailsPage 中,通过构造函数获取参数:
scala
class DetailsPage extends StatelessWidget {
final Item item;
DetailsPage({required this.item});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Details Page')),
body: Center(
child: Text('Item ID: ${item.id}, Name: ${item.name}'),
),
);
}
}
在需要导航到 DetailsPage 的地方,例如在 HomePage 中,通过 context.go 传递参数:
less
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final item = Item(id: 1, name: 'John Doe');
return Scaffold(
appBar: AppBar(title: Text('Home Page')),
body: Center(
child: ElevatedButton(
onPressed: () {
context.go('/details', extra: item);
},
child: Text('Go to Details'),
),
),
);
}
}
对象就飞的使用 extra 来传递吗?我就像用 URL 的方式传参可以吗?
我们当然也可以将复杂数据序列化为 JSON 字符串,然后通过 URL 编码传递。
less
final GoRouter _router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => HomePage(),
),
GoRoute(
path: '/details/:data',
builder: (context, state) {
final jsonString = Uri.decodeComponent(state.params['data']!);
final item = Item.fromJson(json.decode(jsonString));
return DetailsPage(item: item);
},
),
],
);
在需要导航的地方:
less
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final item = Item(id: 1, name: 'John Doe');
final jsonString = json.encode(item.toJson());
final encodedJsonString = Uri.encodeComponent(jsonString);
return Scaffold(
appBar: AppBar(title: Text('Home Page')),
body: Center(
child: ElevatedButton(
onPressed: () {
context.go('/details/$encodedJsonString');
},
child: Text('Go to Details'),
),
),
);
}
}
但是我还是推荐,使用 GoRouter 的 extra 参数传递复杂数据类型,如对象、JSON、List、Map 等。这种方式更简洁和直观。使用URL传递基本数据类型即可,否则可能会导致 URL 过长和不易读。
通过这两种方式,你可以在 Flutter 应用中灵活地传递复杂数据类型到目标页面。
2.3 go_router 的常用API介绍
- 路由信息获取
ini
final router = GoRouter.of(context);
- 路由栈管理
无法直接获取,需要通过 NavigatorObserver 的监听方式自己管理路由栈。
- 路由跳转与替换
go
context.go('/books'); //直接导航到指定路径(path),并替换当前的导航历史记录(不可返回)
context.push('/books'); //直接推入指定路径path),保留当前的导航历史记录(可返回)
context.pushReplacement('/books');
context.goNamed("setting"); //通过命名路由导航到指定路径(name),支持路径参数和查询参数(不可返回)
context.pushNamed('setting'); //通过命名路由导航到指定路径(name),支持路径参数和查询参数 (可返回)
context.pushReplacementNamed('/books');
这里需要强调说一下,注意 go push pushReplacement 的区别,至于后缀 named 这个简单,只是路由定义中的使用 path 还是 name 的区别。
假设有三个页面:HomePage、SettingsPage 和 ProfilePage。
在 HomePage 中使用 context.go('/settings');
之后导航堆栈会如下:
[ /settings ]
用户无法返回到 HomePage 或其他页面。
而在 HomePage 中使用 context.push('/settings');
之后导航堆栈会如下:
[ /home, /settings ]
这是标准的跳转。
当我们在 SettingsPage 中使用 context.pushReplacement('/profile');
之后导航堆栈会如下:
[ /home, /profile ]
不会新加入一个栈,也比较好理解。
此时我们在 ProfilePage 中使用context.go('/settings');
之后导航堆栈会如下:
[ /settings ]
这么看大家就能理解了,使用 context.go 进行导航会重置整个导航堆栈,适用于希望用户无法回到之前页面的场景。使用 context.pushReplacement 进行导航仅替换当前页面,保留之前的导航堆栈,适用于需要保留导航历史的场景。使用 context.push 则是正常的入栈,适合保留廍的导航堆栈。
- 路由返回与弹出
scss
context.canPop(); // 检查是否可以弹出当前路由
context.pop(); // 弹出当前路由
while (context.canPop()) {
context.pop(); // 持续弹出直到根路由
}
那如果我想返回到指定的目标页面可以吗?我想跨多个页面返回可以吗?我想用 popUntilRouteWithName 这个 API 可以吗?
不好意思没有,虽然我们通过 context.go 和 context.pushReplacement 和 context.push 可以控制页面栈的管理,但是我们并不能查询这些栈,我们只能通过 NavigatorObserver 的方式自己实现,具体如何实现可以参考我之前的文章 GetX 的路由封装。
2.4 go_router 的守卫与重定向
go_router 提供了更灵活的守卫和重定向功能。
路由守卫用于在导航到某个页面之前进行一些条件检查,确保用户有权限或满足条件才允许导航到该页面。比如用户未登录时,拦截导航到需要认证的页面,并重定向到登录页面。
less
final GoRouter _router = GoRouter(
routes: <GoRoute>[
GoRoute(
path: '/',
builder: (context, state) => HomePage(),
),
GoRoute(
path: '/login',
builder: (context, state) => LoginPage(),
),
GoRoute(
path: '/profile',
builder: (context, state) => ProfilePage(),
redirect: (state) {
if (!isLoggedIn) {
// 如果用户未登录,重定向到登录页面
return '/login';
}
return null; // 用户已登录,允许导航
},
),
],
);
在这个例子中,如果用户未登录(isLoggedIn 为 false),当尝试导航到 /profile 路径时,会被重定向到 /login 页面。
重定向则是无条件地将一个路径导航到另一个路径。使用重定向可以管理路径的变化,比如如果路径发生改变,或者为了向后兼容旧的路径。
php
final GoRouter _router = GoRouter(
routes: <GoRoute>[
GoRoute(
path: '/',
builder: (context, state) => HomePage(),
),
GoRoute(
path: '/settings',
builder: (context, state) => SettingsPage(),
),
],
redirect: (state) {
final String fullPath = state.subloc;
if (fullPath == '/old-settings') {
// 如果访问旧的设置路径,重定向到新的设置页面
return '/settings';
}
return null; // 没有重定向
},
);
在这个例子中,如果访问 /old-settings 路径,用户会被重定向到 /settings 页面。
都是基于 redirect 实现的功能,特别是守卫拦截是很实用的,比如我如果我想在全部的路由中都加入这个未登录拦截的守卫呢?
我们也不需要再每一个 GoRoute 中加入 redirect 的拦截,我们可以在全局添加一个即可。
less
final GoRouter _router = GoRouter(
routes: <GoRoute>[
GoRoute(
path: '/',
builder: (context, state) => HomePage(),
),
GoRoute(
path: '/login',
builder: (context, state) => LoginPage(),
),
GoRoute(
path: '/profile',
builder: (context, state) => ProfilePage(),
),
GoRoute(
path: '/settings',
builder: (context, state) => SettingsPage(),
),
],
redirect: (GoRouterState state) {
// 获取当前路径
final String fullPath = state.subloc;
// 如果用户未登录,并且当前路径不是 /login,则重定向到 /login
if (!isLoggedIn && fullPath != '/login') {
return '/login';
}
// 如果用户已登录,且当前路径是 /login,则重定向到主页
if (isLoggedIn && fullPath == '/login') {
return '/';
}
return null; // 没有重定向
},
);
是不是很方便呢?
2.5 go_router 的过渡动画
为了提升用户体验,我们可以在页面切换时添加过渡动画。go_router 提供了 pageBuilder 参数来实现自定义过渡动画。
less
inal router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const MyHomePage(),
),
GoRoute(
path: '/setting',
name: '/setting',
// builder: (context, state) => const SettingPage(),
pageBuilder: (context, state) {
return CustomTransitionPage(
key: state.pageKey,
child: const SettingPage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
// 定义页面进入动画
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: child,
);
},
);
},
),
],
);
常用的还有渐变的动画:
php
pageBuilder: (context, state) {
return CustomTransitionPage(
key: state.pageKey,
child: HomePage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
// 定义页面进入动画
return FadeTransition(
opacity: animation,
child: child,
);
},
);
},
其中:
FadeTransition:定义了一个渐隐渐现的动画效果。
SlideTransition:定义了一个从右往左的滑动动画效果。
除此之外你还可以定义骚操作,比如旋转的动画,缩放的动画(PS:我实在想不到有什么应用场景)
scala
class ZoomTransitionPage extends CustomTransitionPage<void> {
ZoomTransitionPage({required Widget child})
: super(
child: child,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return ScaleTransition(
scale: animation,
child: child,
);
},
);
}
class RotateTransitionPage extends CustomTransitionPage<void> {
RotateTransitionPage({required Widget child})
: super(
child: child,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return RotationTransition(
turns: animation,
child: child,
);
},
);
}
如果我想使用自定义的动画可以吗,而不是预设的 RotationTransition ZoomTransitionPage SlideTransition FadeTransition 这些动画?
当然可以:
scala
class MyCustomPanAndRotateTransition extends StatelessWidget {
final Animation<double> animation;
final Widget child;
MyCustomPanAndRotateTransition({required this.animation, required this.child});
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Transform(
transform: Matrix4.identity()
..translate(50 * (1.0 - animation.value))
..rotateZ(animation.value * 0.5 * 3.14),
child: Opacity(
opacity: animation.value,
child: child,
),
);
},
child: child,
);
}
}
定义一个自定义的平移和旋转动画直接在 GoRouter 中使用即可:
php
GoRoute(
path: '/details',
pageBuilder: (context, state) {
return CustomTransitionPage(
key: state.pageKey,
child: DetailsPage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return MyCustomPanAndRotateTransition(animation: animation, child: child);
},
);
},
);
如果我想全局的设置一个统一的动画效果呢?好像也没有。
我们只能通过抽取方法的方式来相对简洁的实现。
less
// 通用的 CustomTransitionPage 构造方法
Page<dynamic> buildPageWithSlideTransition(BuildContext context, GoRouterState state, Widget child) {
return CustomTransitionPage(
key: state.pageKey,
child: child,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: child,
);
},
);
}
final GoRouter _router = GoRouter(
routes: <GoRoute>[
GoRoute(
path: '/',
pageBuilder: (context, state) => buildPageWithSlideTransition(context, state, MyHomePage()),
),
GoRoute(
path: '/setting',
name: 'setting',
pageBuilder: (context, state) => buildPageWithSlideTransition(context, state, SettingPage()),
),
],
);
好吧,基础的功能就演示到这里,下面我们就看看 aoto_router 的用法吧,它会不会有什么不同呢?
三、 auto_router 的使用
auto_router 是一个强大的路由管理库,可以帮助我们简化 Flutter 应用中的路由管理。它提供了强大的API支持,能够方便地实现页面跳转、参数传递、路由守卫、重定向以及过渡动画等功能。
2.1 auto_router 的集成与简单使用
在使用 auto_route 之前,需要在 pubspec.yaml 文件中添加 auto_route 和 auto_route_generator 依赖:
yaml
dependencies:
flutter:
sdk: flutter
auto_route: ^8.0.0
dev_dependencies:
build_runner: ^2.0.0
auto_route_generator: ^8.0.0
定义我们的路由配置
scala
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
part 'app_router.gr.dart';
@AutoRouterConfig(replaceInRouteName: 'Screen,Route')
class AppRouter extends _$AppRouter {
@override
List<AutoRoute> get routes => [
];
}
这里我指定生成的路由信息类为 PageRouter 格式,例如 FirstRoute 。
这是简单的配置,我们可以自定义什么样的文件会转换为什么样的Router,例如 @AutoRouterConfig(replaceInRouteName: 'Page|Screen,PageRoute')
我们需要把全部的页面加上 @RoutePage()
的注解,例如:
scala
@RoutePage()
class DetailsScreen extends ConsumerStatefulWidget {
}
然后我们运行生成代码 dart run build_runner build
默认会在同文件夹下生成对应的路由文件,内部就有对应的 DetailsRoute 这个类,这就是这个页面对应的路由对象了。
然后我们就可以在路由配置中添加对应的页面路由对象:
scala
@AutoRouterConfig(replaceInRouteName: 'Screen,Route')
class AppRouter extends _$AppRouter {
@override
List<AutoRoute> get routes => [
AutoRoute(page: DetailsRoute.page, path: '/detail'),
];
}
最后也别忘记了再入口中配置路由:
scala
class MyApp extends StatelessWidget {
final _appRouter = AppRouter();
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _appRouter.config(
navigatorObservers: () => [
LogedNavigatorObserver(),
],
),
);
}
}
如何使用呢?
csharp
context.router.push(const DetailsRoute());
或者
context.router.navigateNamed('/detail')
当然了,AutoRoute 的主要优势之一就是使用类型安全的路由对象进行页面跳转,也推荐直接拿到对应页面的路由对象就行。
2.2 auto_router 的跳转API与参数传递
2.2.1 基本跳转
使用 push 进行页面跳转:
less
context.router.push(DetailsRoute(id: 1));
2.2.2 通过路径参数传递数据
在路由配置中使用路径参数,并在目标页面中定义:
less
@MaterialAutoRouter(
routes: <AutoRoute>[
AutoRoute(page: HomePage),
AutoRoute(page: DetailsPage, path: '/details/:id'),
],
)
class $AppRouter {}
class DetailsPage extends StatelessWidget {
final int id;
DetailsPage({@PathParam() required this.id});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Details Page')),
body: Center(
child: Text('Item ID: $id'),
),
);
}
}
通过 @PathParam() 定义与接收参数,通过构造传递参数:
less
context.router.push(DetailsRoute(id: 1));
我们也可以通过路径字符串直接跳转,对于基本数据类型,这种方式也是支持携带参数的:
arduino
context.router.pushNamed('/details/1');
但是,如果需要携带对象参数,只能通过生成路由对象来操作,不支持路径跳转。
2.2.3 通过路径参数传递数据 通过构造函数传递复杂数据
定义数据类并通过构造函数传递:
scala
class Item {
final int id;
final String name;
Item({required this.id, required this.name});
}
class DetailsPage extends StatelessWidget {
final Item item;
DetailsPage({required this.item});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Details Page')),
body: Center(
child: Text('Item ID: ${item.id}, Name: ${item.name}'),
),
);
}
}
当我们运行 build_runner 生成对应的路由之后,我们就可以直接传递
less
@MaterialAutoRouter(
replaceInRouteName: 'Page,Route',
routes: <AutoRoute>[
AutoRoute(page: HomePage, initial: true),
AutoRoute(page: SettingPage),
],
)
class $AppRouter {}
对于对象形式的参数传递,不支持通过路径的方式传递参数。我们可以不写 path 属性,只能在应用内跳转:
ini
final item = Item(id: 1, name: 'John Doe');
context.router.push(DetailsRoute(item: item));
那么简单的跳转与带参数的跳转我们就介绍到这里,接下来看看其他的常用的API吧。
2.3 auto_router 的常用API介绍
- 路由信息获取
ini
final router = context.router;
RouteData? routeData = context.router.currentChild;
RouteData? routeData = ontext.router.current
可以获取到路由对象和当前的页面路由对象,可以获取到对应的 name 等信息。
- 路由栈管理
在上面的 go_router 中我们是无法直接获取,需要通过 NavigatorObserver 的监听方式自己管理路由栈。
在 auto_router 中我们可以根据 context.router.stack;
的方式获取当前全部的路由栈。
那么我们就可以很方便的获取到目标页面是否已经存在在栈中,从而进行一些逻辑判断。
- 路由跳转与替换
less
context.router.push(DetailsRoute(id: 1)); // push 总是将新路由推入堆栈,保留导航历史,方便返回。
context.router.pushNamed('/details/1'); // 支持通过路径的方式跳转
context.router.navigate(DetailsRoute(id: 1)) //navigate 会尝试在导航堆栈中找到目标路由,如果存在则返回到该路由,否则就像 push 一样添加新路由。
context.router.navigateNamed(DetailsRoute(id: 1))
context.router.replace(DetailsRoute(id: 1)); //replace 用新路由替换当前路由。
context.router.replaceNamed('/details/1');
示例:
假设有三个页面:HomePage、SettingsPage 和 ProfilePage。
使用 push 在 HomePage 中使用 context.router.push(SettingsRoute()); 之后导航堆栈会如下:
[ /home, /settings ]
标准的堆栈式跳转,保留历史记录,可返回到 HomePage。
使用 replace 在 HomePage 中使用 context.router.replace(SettingsRoute()); 之后导航堆栈会如下:
[ /settings ]
替换当前页面,不保留 HomePage 的历史记录。
使用 navigate 在 HomePage 中使用 context.router.navigate(SettingsRoute());:
如果 SettingsPage 已在导航堆栈中,则返回到它,而不重新添加。
如果不在堆栈中,则效果与 push 相同。
那么如果在 SettingsPage 中使用 context.router.replace(ProfileRoute()); 之后导航堆栈会如下:
[ /home, /profile ]
替换当前页面,不增加新的栈。
那么如果在 SettingsPage 中使用 context.router.navigate(HomeRouter()); 之后导航堆栈会如下:
[ /home ]
此时的 navigate 就不是 push 的效果而是 pop 的效果了。
通过这些对比,开发者可以更直观地理解 auto_route 中不同导航方法的特点,以便根据需求选择合适的跳转方式。
- 路由的返回与多级返回
在 auto_route 中,处理页面导航和路由返回是非常常见的操作。下面我们将详细介绍一些常用的路由返回方法,以及如何使用它们。
- pop
从堆栈中弹出当前页面,并导航到前一个页面:
ini
context.router.pop();
- maybePop
尝试从堆栈中弹出当前页面,如果堆栈中只有一个页面,则不会执行任何操作。
ini
context.router.maybePop();
当你不确定当前页面是否是堆栈中的最后一个页面时,可以使用 maybePop 来避免程序崩溃或导航异常。
- popUntilRoot
弹出堆栈中的所有页面,直到返回到根页面。
ini
context.router.popUntilRoot();
当你需要从子页面快速返回到根页面(通常是应用的首页)时,该方法非常有用。
- popUntilRouteWithName
弹出堆栈中的页面,直到找到指定名称的路由。
arduino
context.router.popUntilRouteWithName('HomeRoute');
当你需要返回到某个特定的页面,而这个页面并不是根页面时,可以使用 popUntilRouteWithName 来精确控制导航行为。
- popUntil
根据条件弹出堆栈中的页面,直到满足条件。
ini
context.router.popUntil((route) => route.settings.name == 'HomeRoute')
当你需要基于自定义条件返回到特定页面时,可以使用 popUntil 可以自定义条件而不是我这里用的 name 做判断,如果只是用 name 做条件可以用 popUntilRouteWithName 的方式。
- pushAndRemoveUntil
将新页面推入堆栈,并弹出所有不满足条件的页面。
ini
context.router.pushAndRemoveUntil(
ProfileRoute(),
(route) => route.settings.name == 'HomeRoute'
);
当你需要跳转到一个新页面并清理堆栈中多余页面时,可以使用 pushAndRemoveUntil。
- removeLast
从堆栈中移除最后一个页面,但不会导航到前一个页面。
ini
context.router.removeLast();
当你需要移除最后一个页面,但不想立即导航到其他页面时,可以使用 removeLast 的方式。
通过了解和使用这些 API 方法,你可以更灵活地控制应用的导航和返回行为,提高用户体验。
2.4 auto_router 的守卫与重定向
在 auto_route 中,我们可以通过 RouteGuard 实现导航守卫功能,以在导航到某个页面之前进行条件检查,比如用户是否已登录。如果不满足条件,可以重定向到其他页面,例如登录页面。
scala
import 'package:auto_route/auto_route.dart';
class AuthGuard extends AutoRouteGuard {
@override
void onNavigation(NavigationResolver resolver, StackRouter router) {
if (!isLoggedIn) {
// 如果用户未登录,重定向到登录页面
router.push(LoginRoute());
} else {
// 允许导航
resolver.next(true);
}
}
}
在你的路由设置中,使用 guards 参数:
less
@AutoRouterConfig(replaceInRouteName: 'Page|Screen,PageRoute')
class AppRouter extends _$AppRouter {
@override
List<AutoRoute> get routes => [
AutoRoute(page: MyHomePageRoute.page, path: '/', initial: true),
AutoRoute(page: SettingPageRoute.page, path: '/setting/:id', guards: [AuthGuard()]),
];
}
这里和 go_router 不同的是,它没有全局拦截的配置,我们为了方法可以抽取方法进行配置:
javascript
AutoRoute guardedRoute({required PageRouteInfo Function() page, required String path}) {
return AutoRoute(page: page, path: path, guards: [AuthGuard()]);
}
然后再需要拦截的地方设置守卫:
less
@AutoRouterConfig(replaceInRouteName: 'Page|Screen,PageRoute')
class AppRouter extends _$AppRouter {
@override
List<AutoRoute> get routes => [
guardedRoute(page: MyHomePageRoute.page, path: '/'),
guardedRoute(page: SettingPageRoute.page, path: '/setting/:id'),
];
}
这种方式可以减少手动配置的重复性,并提高代码的可维护性。
2.5 auto_router 的过渡动画
默认实现的 AutoRoute 使用平台自带的过渡动画,我们如果想自定义页面的动画,我们可以使用 CustomRoute 对象,并且配置对应的 transitionsBuilder 对象。
php
@AutoRouterConfig(replaceInRouteName: 'Page|Screen,PageRoute')
class AppRouter extends _$AppRouter {
Widget fadeInTransition(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return FadeTransition(
opacity: animation,
child: child,
);
}
@override
List<AutoRoute> get routes => [
CustomRoute(
page: MyHomePageRoute.page,
path: '/',
initial: true,
transitionsBuilder: fadeInTransition,
),
CustomRoute(
page: SettingPageRoute.page,
path: '/setting/:id',
transitionsBuilder: fadeInTransition,
),
];
}
那么如果我想实现从右往左平移并且淡入淡出的动画,可以结合使用 SlideTransition 和 FadeTransition。
总结
我们简单的回顾了 go_router
与 auto_route
的简单与常用的用法,可以看出 go_router
相对比较而言轻量与简洁,适合中小项目,而 auto_route
的功能更全面 API 更丰富,更适合大型项目。
选择哪一个路由库更多取决于个人偏好和项目需求。如果项目中对类型安全和代码生成有偏好,并且愿意为此承担额外的学习成本,auto_route
可能是更好的选择。而如果你优先考虑易用性和快速上手,go_router
可能更适合。
最近我的项目可能就考虑使用 Riverpod 的注解生成,那么我选择 auto_route
也是顺便的事情,反正都是生成代码。个人也比较喜欢 auto_route
的跳转与返回等 API 感觉更加的灵活方便。
那本文的代码比较简单,相对比较基础,并且全部的代码也已经在文中展出,这里就不放出项目链接了,后期会直接给出 Riverpod + autoRouter 的项目 Dmeo 吧。
如果我的文章有错别字,不通顺的,或者代码、注释、有错漏的地方,理解不正确的地方,同学们都可以指出修正。
今天的分享就到这里啦,当然如果你有其他的更多的更好的实现方式推荐,也希望大家能评论区交流一起学习进步。
如果感觉本文对你有一点的启发和帮助,还望你能点赞
支持一下,你的支持对我真的很重要。
Ok,这一期就此完结。