Flutter路由管理插件hyper_router的使用
Flutter路由管理插件hyper_router的使用
HYPER_ROUTER
HYPER_ROUTER 是一个声明式的、类型安全的、无需代码生成的 Flutter 路由管理插件。
特点
- 基于值的导航
- 声明式
- 路由守卫
- 嵌套路由与状态保存
- 从路由返回值
- 可选的 URL 支持
- 高度可扩展
- 无需代码生成
概述
- 声明路由树:每个节点都有一个唯一的键。
- 访问控制器:使用
HyperRouter.of(context)
或context.hyper
与路由器交互。 - 导航:使用
context.hyper.navigate(<key>)
推送新路由到栈中。 - 弹出路由:使用
context.hyper.pop
或Navigator.of(context).pop
返回上一个屏幕。
路由树配置
final router = HyperRouter(
initialRoute: HomeScreen.routeName,
routes: [
ShellRoute(
shellBuilder: (context, controller, child) =>
MainTabsShell(controller: controller, child: child),
tabs: [
NamedRoute(
screenBuilder: (context) => const HomeScreen(),
name: HomeScreen.routeName,
children: [
NamedRoute(
screenBuilder: (context) => const ProductListScreen(),
name: ProductListScreen.routeName,
children: [
ValueRoute<ProductRouteValue>(
screenBuilder: (context, value) =>
ProductDetailsScreen(value: value),
),
],
),
],
),
NamedRoute(
screenBuilder: (context) => const GuideScreen(),
name: GuideScreen.routeName,
),
NamedRoute(
screenBuilder: (context) => const SettingsScreen(),
name: SettingsScreen.routeName,
),
],
),
],
);
常见路由类型
- NamedRoute:基本路由,关联一个唯一名称。
- ValueRoute<T>:允许传递数据到另一个屏幕的路由。
- ShellRoute:包含嵌套导航器的路由,例如底部导航栏。
NamedRoute
用于简单的屏幕间导航,不需要传递数据。
- 声明路由名称:
class HomeScreen extends StatelessWidget {
static const routeName = RouteName('home');
// ...
}
- 导航:
HyperRouter.of(context).navigate(HomeScreen.routeName);
// 或者,为了方便:
// context.hyper.navigate(HomeScreen.routeName);
ValueRoute<T>
如果需要在导航时传递数据,使用 ValueRoute<T>
。
- 声明值类型:
class ProductRouteValue extends RouteValue {
const ProductRouteValue(this.product);
final Product product;
}
- 导航:
context.hyper.navigate(ProductRouteValue(
Product(/*...*/)
));
ShellRoute
用于创建底部导航栏。
-
参数:
shellBuilder
:包裹子路由并显示标签栏的屏幕。tabs
:将显示在壳内的路由。
-
使用
ShellController
:setTabIndex(index)
:切换到指定索引的标签。tabIndex
:获取当前活动标签的索引。
示例:
import 'package:flutter/material.dart';
import 'package:hyper_router/hyper_router.dart';
class TabsShell extends StatelessWidget {
const TabsShell({
required this.controller,
required this.child,
super.key,
});
final Widget child;
final ShellController controller;
@override
Widget build(BuildContext context) {
final i = controller.tabIndex;
return Scaffold(
body: child,
bottomNavigationBar: NavigationBar(
onDestinationSelected: (value) {
controller.setTabIndex(value);
},
selectedIndex: controller.tabIndex,
destinations: [
NavigationDestination(
icon: Icon(i == 0 ? Icons.home_outlined : Icons.home),
label: "Home",
),
NavigationDestination(
icon: Icon(i == 1 ? Icons.shopping_bag_outlined : Icons.shopping_bag),
label: "Cart",
),
NavigationDestination(
icon: Icon(i == 2 ? Icons.settings_outlined : Icons.settings),
label: "Settings",
),
],
),
);
}
}
从路由返回值
- 接收结果:
final result = await context.hyper.navigate(FormScreen.routeName);
- 返回结果:
// FormScreen
context.hyper.pop(value);
原生的 Flutter push
和 pop
也适用。例如,显示对话框:
final result = await showDialog(Dialog(...));
Navigator.of(context).pop(value);
守卫
使用重定向回调来控制基于条件(如认证状态)的导航流:
final router = HyperRouter(
redirect: (context, state) {
final authCubit = context.read<AuthCubit>();
// 检查用户是否未登录且尝试访问认证路由
if (!authCubit.state.authenticated &&
state.stack.containsNode(AuthwalledScreen.routeName.key)) {
return AuthScreen.routeName; // 重定向到认证
}
return null; // 不需要重定向
},
// ...
);
state.stack
:表示即将的导航栈。第一个元素在底部,最后一个在顶部。stack.containsNode
:检查具有所提供键的路由是否存在。注意它需要显式地提供key
。- 返回:
- 要重定向用户的路由键。这是与
navigate
相同的值。 null
,如果不需要重定向。
- 要重定向用户的路由键。这是与
启用 URL
有两个用例需要 URL 支持:Web 应用和深度链接。由于大多数 Flutter 应用针对移动平台,且深度链接通常只覆盖少数目的地,HYPER_ROUTER 设计为使 URL 支持可选。
Web
默认情况下,应用在浏览器中运行良好,但 URL 不会更新。要修复这一点,将 enableUrl
属性设置为 true
:
final router = HyperRouter(
enableUrl: true,
// ...
);
现在,你需要确保每个路由都可以解析为 URL 段。NamedRoute
默认支持解析,但 ValueRoute
需要提供解析器。
创建 URL 解析器
这里我们为 ProductRouteValue
创建一个解析器。我们希望 URL 看起来像这样:home/products/<productID>
。解析器负责 <productId>
段:
class ProductSegmentParser extends UrlSegmentParser<ProductRouteValue> {
@override
ProductRouteValue? decodeSegment(SegmentData segment) {
return ProductRouteValue(segment.name);
}
@override
SegmentData encodeSegment(ProductRouteValue value) {
return SegmentData(name: value.productId);
}
}
你可以选择性地将查询参数提供给 SegmentData
(queryParams
字段)。它们将放在 URL 的末尾。如果堆栈中包含多个带有查询参数的路由,它们将被合并。
decodeSegment
应该在不识别段时返回 null
。
segment.state
存储在浏览器的历史记录中。你可以将不想在 URL 中显示的数据放在这里,当用户使用浏览器的后退和前进按钮导航时,这些数据将被恢复。
深度链接
TODO(尽管可能已经实现)
创建自定义路由
我设计了这个包,使其高度可扩展,以便为任何奇怪和不寻常的用例创建路由。例如,在 demo app 中,我创建了一个响应式的 list-detail 视图:在宽屏上显示列表和详细页面并排显示(类似于壳路由),在小屏幕上按顺序显示。
路由器的工作原理:
- 从目标路由到根遍历路由树,构建一个
RouteNode
的链表。 - 每个节点负责构建自己的页面及其所有后续页面。这发生在
createPages
方法中,该方法返回页面列表。NamedRoute
和ValueRoute
只是将自己的页面和所有后续页面堆叠在一起:Iterable<Page> createPages(BuildContext context) { final page = buildPage(context); return [page].followedByOptional(next?.createPages(context)); }
ShellRoute
只将自己的页面放入列表,而其所有子路由则放入壳页面内的嵌套导航器中。
- 要创建自定义路由,需要覆盖这两个类:
HyperRoute
和RouteNode
。
这应该足以理解 demo app 中的示例。
示例代码
import 'package:example/features/demos/guard/auth_screen.dart';
import 'package:example/features/demos/guard/authwalled_screen.dart';
import 'package:example/features/demos/guard/state/auth_cubit.dart';
import 'package:example/features/demos/guard/state/auth_state.dart';
import 'package:example/features/navigation/router.dart';
import 'package:example/features/utils/material_match.dart';
import 'package:flex_color_scheme/flex_color_scheme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (context) => AuthCubit()),
],
child: BlocListener<AuthCubit, AuthState>(
listener: (context, state) {
final c = router.controller;
if (!state.authenticated &&
c.stack.containsNode(AuthwalledScreen.routeName.key)) {
c.navigate(AuthRouteValue(c.stack.last().value));
}
},
child: MaterialApp.router(
theme: _createTheme(),
routerConfig: router,
),
),
);
}
ThemeData _createTheme() {
final theme = FlexColorScheme.dark(
useMaterial3: true,
scheme: FlexScheme.deepPurple,
).toTheme;
return theme.copyWith(
cardTheme: CardTheme(
margin: const EdgeInsets.all(8),
color: theme.colorScheme.surfaceVariant,
clipBehavior: Clip.antiAlias,
elevation: 0,
shape: RoundedRectangleBorder(
side: BorderSide.none,
borderRadius: BorderRadius.circular(32),
),
),
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(32),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 32),
hoverColor: theme.colorScheme.tertiaryContainer,
filled: true,
),
textButtonTheme: TextButtonThemeData(
style: ButtonStyle(
textStyle: materialMatch(
all: const TextStyle(
fontSize: 16,
),
),
padding: materialMatch(all: const EdgeInsets.all(24)),
splashFactory: InkSparkle.splashFactory,
),
),
iconButtonTheme: const IconButtonThemeData(
style: ButtonStyle(splashFactory: InkSparkle.splashFactory),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: ButtonStyle(
textStyle: materialMatch(
all: const TextStyle(
fontSize: 16,
),
),
padding: materialMatch(all: const EdgeInsets.all(24)),
splashFactory: InkSparkle.splashFactory,
),
),
filledButtonTheme: FilledButtonThemeData(
style: ButtonStyle(
textStyle: materialMatch(
all: const TextStyle(
fontSize: 16,
),
),
padding: materialMatch(all: const EdgeInsets.all(24)),
splashFactory: InkSparkle.splashFactory,
),
),
dialogTheme: DialogTheme(
actionsPadding: const EdgeInsets.all(32),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(32),
),
),
);
}
}
更多关于Flutter路由管理插件hyper_router的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter路由管理插件hyper_router的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何在Flutter项目中使用hyper_router
进行路由管理的示例代码。hyper_router
是一个强大的路由管理插件,它提供了灵活的路由配置和状态管理功能。
首先,确保你已经在pubspec.yaml
文件中添加了hyper_router
依赖:
dependencies:
flutter:
sdk: flutter
hyper_router: ^x.y.z # 请将x.y.z替换为最新的版本号
然后运行flutter pub get
来安装依赖。
接下来,让我们创建一个简单的Flutter项目,并使用hyper_router
来管理路由。
1. 初始化HyperRouter
在main.dart
文件中,首先初始化HyperRouter
:
import 'package:flutter/material.dart';
import 'package:hyper_router/hyper_router.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return HyperRouter(
routes: {
'/': (context) => HomeScreen(),
'/details': (context, {required String id}) => DetailsScreen(id: id),
},
initialRoute: '/',
builder: (context, widget) => MaterialApp(
home: widget,
navigatorKey: HyperRouter.navigatorKey, // 用于全局导航
),
);
}
}
2. 创建HomeScreen和DetailsScreen
接下来,创建两个简单的屏幕:HomeScreen
和DetailsScreen
。
import 'package:flutter/material.dart';
import 'package:hyper_router/hyper_router.dart';
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Screen'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
// 导航到详情页面,并传递一个id参数
HyperRouter.of(context).navigate('/details', arguments: {'id': '123'});
},
child: Text('Go to Details'),
),
),
);
}
}
class DetailsScreen extends StatelessWidget {
final String id;
DetailsScreen({required this.id});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Details Screen'),
),
body: Center(
child: Text('Details for ID: $id'),
),
);
}
}
3. 使用全局导航(可选)
如果你需要在应用的任何地方进行全局导航,可以使用HyperRouter.navigatorKey
。例如,你可以在MyApp
的顶部添加一个全局按钮来演示这一点:
class MyApp extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@override
Widget build(BuildContext context) {
return HyperRouter(
routes: {
'/': (context) => HomeScreen(),
'/details': (context, {required String id}) => DetailsScreen(id: id),
},
initialRoute: '/',
builder: (context, widget) => MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Flutter App'),
actions: [
IconButton(
icon: Icon(Icons.navigate_next),
onPressed: () {
navigatorKey.currentState?.pushNamed('/details', arguments: {'id': '456'});
},
),
],
),
body: widget,
),
navigatorKey: navigatorKey, // 用于全局导航
),
);
}
}
请注意,在上面的示例中,我们将navigatorKey
定义在MyApp
类中,并将其传递给MaterialApp
的navigatorKey
属性。这样,你就可以在应用的任何地方使用这个navigatorKey
来进行全局导航。
总结
通过上述步骤,你已经成功地在Flutter项目中使用hyper_router
进行了路由管理。这个插件提供了强大的功能,可以帮助你轻松地管理应用的路由和状态。如果你需要更高级的功能,比如嵌套路由、守卫(guard)、动画等,请参考hyper_router
的官方文档。