Flutter路由管理插件owlet_router的使用
Flutter路由管理插件owlet_router的使用
Owlet Router

owlet_router
是一个路由管理器。它利用路由构建器来构建路由。
它的设计目的包括:
- 提供一个清晰且易于定义的路由管理器,便于阅读和使用。
- 基于基础的Flutter路由构建,允许集成各种页面路由类型,并能够自定义和扩展路由。
- 支持模块化路由,使路由可以分段并创建独立的路由。
- 提供检查、阻止或重定向路由的能力,使其在推送之前进行处理。
目录
开始使用
在 dependencies
中添加以下依赖项:
dependencies:
owlet_router: '0.0.2'
导入包:
import 'package:owlet_router/router.dart';
用法
创建顶级路由(根路由)的 AppRoute
类。此路由有两个直接子路由 splash
和 home
。
class AppRoute extends RouteBase {
AppRoute() : super.root(); // 默认设置根段为'/'
final splash = MaterialRouteBuilder(
'/', pageBuilder: (context, settings) => const SplashPage());
final home = MaterialRouteBuilder(
'/home', pageBuilder: (context, settings) => const HomePage());
final profiles = ProfileRoute('/profile');
List<RouteBase> get children => [splash, home, profiles];
}
ProfileRoute
是 AppRoute
的子路由模块。它有两个子路由,详情页 (/profile/
) 和更新个人资料页 (/profile/update-profile
)。
class ProfileRoute extends RouteBase {
ProfileRoute(super.segment);
final details = MaterialRouteBuilder(
'/', pageBuilder: (context, settings) => const DetailsPage());
final update = MaterialRouteBuilder(
'/update-profile', pageBuilder: (context, settings) => const UpdateProfilePage());
List<RouteBase> get children => [details, update];
}
要使用此路由,必须将顶级路由(AppRoute
)注入到您的导航器中使用 NavigationService
。
final appRoute = AppRoute(); // 根路由
final service = NavigationService(
navigationKey: GlobalKey(),
routeObservers: [ /* your observers */ ],
initialRoute: '/',
root: appRoute,
unknownRoute: owletDefaultUnknownRoute);
// ...
// 在根导航器中使用
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: service.routerConfig,
/// ...
);
}
// *************************************************
// 在嵌套导航器中使用
Widget build(BuildContext context) {
return OwletNavigator(service);
}
要推送 profile
路由,您可以使用导航器的常规 pushNamed
方法:
final result = await Navigator.of(context).pushNamed('/profile/');
或者从路由字段调用:
final result = await appRoute.profiles.pushNamed(
args: //...,
params: // ... 查询参数
fragment: // #route-fragment,
);
功能
1. NavigationService
NavigationService
提供了新的 Navigator
所需的方法:
Navigator(
key: service.navigationKey,
initialRoute: service.initialRoute,
observers: <NavigatorObserver>[service.history, ...service.routeObservers],
onGenerateRoute: service.onGenerateRoute,
onPopPage: service.onPopPage,
onUnknownRoute: service.onUnknownRoute,
);
1.1. Owlet Navigator
OwletNavigator
是一个自定义实现的 Navigator
,旨在提供诸如路由守卫和命名函数等高级功能。默认情况下,RouterDelegate
使用 OwletNavigator
。
Widget build(BuildContext context) {
return OwletNavigator(service);
}
2. Provider
为了优化性能,可以在 Navigator 中将 NavigationService
设置为单例。这可以通过创建单例变量或使用依赖注入(如 GetIt)来实现。
NavigationServiceProvider
允许您在上下文中访问 NavigationService
。
final service = NavigationService.of(context);
此外,将路由作为静态字段创建可以提高性能。但是,请注意,静态路由不能动态更改其父路由。没有必要将每个路由都设置为静态字段,模块管理器功能仍然可以与单独的路由一起工作,允许您为应用程序的每个部分创建代表路由。
要在当前上下文中查找对应的 RouteBase
对象,请遍历路由树。
final route = RouteBase.of<ROUTE_TYPE>(context);
但是,这种方法可能会导致最坏情况下的时间复杂度为 O(n * k),其中’n’表示路由树的平均深度,'k’表示 NavigationService
层的数量。前面提到的另一种方法是从部分路由直接确定路由类型。
final route = profiles.findType<ROUTE_TYPE>();
3. 路由历史
NavigationService
提供了一个记录当前路由的路由历史观察者。
void listenHistory() {
final history = service.history;
history.addListener(() {
/// 监听路由变化
if (kDebugMode) {
print(history.current);
}
});
final currentRoute = history.current; // 获取当前显示的路由
bool isAppeared = history.contains('/home'); // 检查 /home 路由是否显示
// 查找符合条件的路由,如果需要对其执行某些操作(例如使用 Navigator.replacement)
final route = history.nearest(/* condition */);
}
4. 未知路由
如果找不到路由或在构造过程中发生错误,将使用未知路由作为替代。
final service = NavigationService(
// ...
unknownRoute: yourRoute
);
5. 路由
owlet_router
利用模块架构进行路由。要定义路由,请创建一个继承 RouteBase
的类。在此类内部,您可以指定该路由的子路由。重复此过程以扩展路由树。
final appRoute = AppRoute();
// ..
class AppRoute extends RouteBase {
AppRoute() : super.root(); // 如果此路由是根路由
final splash = MaterialRouteBuilder(
'/', pageBuilder: (context, settings) => const SplashPage());
final home = MaterialRouteBuilder(
'/home', pageBuilder: (context, settings) => const HomePage());
final items = ListItemRoute('/item');
[@override](/user/override)
List<RouteBase> get children => [splash, home, items];
}
class ListItemRoute extends RouteBase {
ListItemRoute(super.segment);
final list = MaterialRouteBuilder(
'/list', pageBuilder: (context, settings) => const ListItemPage());
late final detail = RouteGuard(
routeBuilder: RouteBuilder<String, dynamic>(
'/detail',
builder: (settings) {
if (settings.arguments is String) {
return MaterialPageRoute(
settings: settings,
builder: (context) => DetailPage(item: settings.arguments as String),
);
}
return CancelledRoute(false);
},
),
routeGuard: (context, route) async {
if (!navigatorServices.history.contains(list.path)) {
Navigator.push(context, list.noAnimationBuilder()!);
}
return route;
},
);
[@override](/user/override)
List<RouteBase> get children => [detail, list];
}
警告
不要忘记将您的路由添加到
List<RouteBase> get children
获取器中。如果没有注册,该路由将无法找到。
5.1. 路由构建器
将 RouteBase
视为文件夹;它不创建任何东西,因此是一个不可启动的路由。要创建页面路由,我们使用 RouteBuilder
,这是一个可启动的路由。
final page1 = RouteBuilder(
'/page1',
builder: (settings) =>
MaterialPageRoute(
settings: settings,
builder: (context) => Page1(arguments: settings.arguments),
),
);
RouteBuilder
允许您自定义 PageRoute
,并且我们已经创建了一些自定义的 RouteBuilder
。您还可以通过覆盖 RouteBuilder.builder
方法来自定义 PageRoute
。
5.2. 如何工作?
-
路由路径是什么? 要获取
splash
的路径,可以使用splash.path
方法。splash
的路径是由其父路径与其自身路径连接生成的。如果结果包含重复的斜杠 (//
),它们将合并为一个。因此,如果您希望段具有与父路由相同的路径,只需将其段设置为单个斜杠 (/
)。 -
推送新路由 如果知道路径,一种简化的方法是使用
Navigator.pushNamed
将其推送到导航器。Navigator.of(context).pushNamed('/home'); // 或者 Navigator.of(context).pushNamed(appRoute.home.path);
RouteBuilder
的扩展提供了另一种方法来推送它。appRoute.home.pushNamed(context);
-
参数和结果
RouteBuilder<A, T>.pushNamed
方法提供了有关路由的参数类型和结果类型的信息。final page2 = RouteBuilder<String, bool>( '/page2', builder: (settings) { final greeting = settings.arguments as String?; return MaterialPageRoute( builder: (context) => TestApp(content: greeting), ); }, ); // ... final bool result = await page2.pushName(context, args: /* 字符串类型是必需的*/);
-
在模块中 如果
ListItemRoute
位于模块包中,则无法访问appRoute
,因为它是在主包中定义的。相反,您可以使用RouteBase.of<ListItemRoute>()
来特定地检索您的items
路由。这种方法促进了不同包之间代码的更大独立性。
5.3. 自定义路由构建器
-
MaterialRouteBuilder
: 它会创建一个MaterialPageRoute
。 -
CupertinoRouteBuilder
: 它会创建一个CupertinoPageRoute
。 -
NoTransitionRouteBuilder
: 它会创建一个没有过渡效果的PageRoute
。final splash = MaterialRouteBuilder('/', pageBuilder: (context, settings) => const SplashPage());
-
RouteGuard
:- 它仅与
OwletNavigator
一起工作。RouteGuard
提供了一个在推送路由前调用的routeGuard
方法,允许您在推送前修改路由。 - 为了防止路由被推送,返回一个
CancelledRoute
。要重定向它,返回另一个路由或在routeGuard
方法中使用RedirectRoute('redirect-path')
。
final detail = RouteGuard( route: RouteBuilder<String, dynamic>( '/detail', builder: (settings) { if (settings.arguments is String) { return MaterialPageRoute( settings: settings, builder: (context) => DetailPage(item: settings.arguments as String)); } return CancelledRoute(false); }, ), routeGuard: (context, route) async { if (!navigatorServices.history.contains(list.path)) { Navigator.push(context, list.noAnimationBuilder()!); } return route; }, );
- 它仅与
注意
RouteGuard
仅适用于以下函数:
Navigator.push
Navigator.pushNamed
Navigator.popAndPushNamed
Navigator.pushReplacement
Navigator.pushReplacementNamed
Navigator.pushAndRemoveUntil
Navigator.pushNamedAndRemoveUntil
-
NamedFunctionRoute
: 一种理想的方法是命名一个函数并通过导航器调用它。当推送NamedFunctionRoute
时,不会添加路由;而是调用定义的函数,其结果将被返回。类似于
RouteGuard
,它也仅与OwletNavigator
一起工作。final action = NamedFunctionRouteBuilder( '/action', callback: (context, route) => print('Hello World'), );
注意
NamedFunctionRouteBuilder
仅适用于以下函数:
Navigator.pushNamed
Navigator.popAndPushNamed
5.4. 排查问题
- 当使用
appRoute.profiles.update.path
方法获取路由时,预期路径为/home/profiles/update
,但结果为/update
,请确保已在profile
路由的children
获取器中包含此路由。 - 当更新路由的子路由时遇到问题 1,即使它已经在
children
列表中存在,也要确保在更改路由后调用了repair
方法。 - 为了获得最佳性能,路由器应该更稳定,更改次数更少。因此,如果您在更改路由并执行热加载时遇到问题,请考虑使用 ‘热重启’ 方法来解决此问题。虽然
repair
方法可能有效,但它会消耗额外资源,因此建议在发布模式下避免使用它。
嵌套导航器
要使用嵌套导航器。创建一个带有 OwletNavigator
的 NestedPage
:
static final nestedService = NavigatorService( /*...*/ );
class NestedPage extends StatefulWidget {
/// ...
}
class NestedPageState extends State<NestedPage> {
/// ...
Widget build(BuildContext context) {
return OwletNavigator(nestedService);
}
}
在根路由中创建一个具有 NestedService
的字段:
class AppRoute extends RouteBase {
// ...
final nestedPage = NestedService(
service: nestedService,
route: MaterialRouteBuilder('/nested', pageBuilder: (context, settings) => const NestedPage()));
}
每当您推送一个路由作为 /nested/sub-path/**
,如果 NestedPage
已经存在于导航器中,nestedService
将被推送到路径 /sub-path/**
。否则,一个新的 NestedPage
将被推送到导航器。
Navigator.of(context, rootNavigator: true).pushNamed('/nested/sub-path/**');
嵌套路由
类似于嵌套导航器,嵌套路由有助于将路由分为两个组件。实际路由被注入到导航服务中,而剩余的段作为页面小部件的参数。
class ItemDetailPageState extends State<ItemDetailPage> {
late final NestedRoute nestedRoute;
[@override](/user/override)
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
nestedRoute = RouteBase.of<NestedRoute>(context);
nestedRoute.addListener(onRouteNotifier);
});
}
[@override](/user/override)
void dispose() {
nestedRoute.removeListener(onRouteNotifier);
super.dispose();
}
void onRouteNotifier() {
print(nestedRoute.value as RouteSettings?);
}
}
//....
class AppRoute extends RouteBase {
// ...
final itemDetail = NestedRoute(
route: MaterialRouteBuilder('/item-detail', pageBuilder: (context, settings) => const ItemDetailPage()));
}
更多关于Flutter路由管理插件owlet_router的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter路由管理插件owlet_router的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
owlet_router
是一个用于 Flutter 应用的路由管理插件,它提供了一种简单且灵活的方式来管理应用程序中的页面导航。以下是如何使用 owlet_router
插件的基本步骤:
1. 添加依赖
首先,你需要在 pubspec.yaml
文件中添加 owlet_router
的依赖:
dependencies:
flutter:
sdk: flutter
owlet_router: ^1.0.0 # 请使用最新版本
然后运行 flutter pub get
来安装依赖。
2. 初始化路由
在 main.dart
文件中初始化 owlet_router
。
import 'package:flutter/material.dart';
import 'package:owlet_router/owlet_router.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
title: 'Owlet Router Example',
// 使用 OwletRouter 来管理路由
onGenerateRoute: OwletRouter.onGenerateRoute,
initialRoute: '/',
);
}
}
3. 定义路由
在 owlet_router
中,你可以通过 OwletRouter
类来定义路由。你可以在应用的任何地方定义路由,但通常会在 main.dart
或一个单独的路由配置文件中定义。
import 'package:owlet_router/owlet_router.dart';
class Routes {
static const String home = '/';
static const String about = '/about';
static const String profile = '/profile';
}
void configureRoutes() {
OwletRouter.configure([
OwletRoute(
path: Routes.home,
builder: (context, params) => HomePage(),
),
OwletRoute(
path: Routes.about,
builder: (context, params) => AboutPage(),
),
OwletRoute(
path: Routes.profile,
builder: (context, params) => ProfilePage(),
),
]);
}
在 main.dart
中调用 configureRoutes
:
void main() {
configureRoutes();
runApp(MyApp());
}
4. 页面导航
你可以使用 OwletRouter
提供的方法进行页面导航。
import 'package:flutter/material.dart';
import 'package:owlet_router/owlet_router.dart';
class HomePage extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
// 导航到 About 页面
OwletRouter.pushNamed(context, Routes.about);
},
child: Text('Go to About'),
),
ElevatedButton(
onPressed: () {
// 导航到 Profile 页面
OwletRouter.pushNamed(context, Routes.profile);
},
child: Text('Go to Profile'),
),
],
),
),
);
}
}
5. 传递参数
你可以在导航时传递参数,并在目标页面中获取这些参数。
OwletRouter.pushNamed(context, Routes.profile, arguments: {'userId': 123});
// 在 ProfilePage 中获取参数
class ProfilePage extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
final Map<String, dynamic> args = OwletRouter.of(context).arguments;
final int userId = args['userId'];
return Scaffold(
appBar: AppBar(
title: Text('Profile'),
),
body: Center(
child: Text('User ID: $userId'),
),
);
}
}
6. 其他功能
owlet_router
还提供了其他一些功能,例如:
- 路由拦截:你可以在路由跳转前进行拦截,例如检查用户是否登录。
- 嵌套路由:支持嵌套路由,可以用于复杂的页面结构。
7. 示例代码
以下是一个完整的示例代码:
import 'package:flutter/material.dart';
import 'package:owlet_router/owlet_router.dart';
void main() {
configureRoutes();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
title: 'Owlet Router Example',
onGenerateRoute: OwletRouter.onGenerateRoute,
initialRoute: Routes.home,
);
}
}
class Routes {
static const String home = '/';
static const String about = '/about';
static const String profile = '/profile';
}
void configureRoutes() {
OwletRouter.configure([
OwletRoute(
path: Routes.home,
builder: (context, params) => HomePage(),
),
OwletRoute(
path: Routes.about,
builder: (context, params) => AboutPage(),
),
OwletRoute(
path: Routes.profile,
builder: (context, params) => ProfilePage(),
),
]);
}
class HomePage extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
OwletRouter.pushNamed(context, Routes.about);
},
child: Text('Go to About'),
),
ElevatedButton(
onPressed: () {
OwletRouter.pushNamed(context, Routes.profile, arguments: {'userId': 123});
},
child: Text('Go to Profile'),
),
],
),
),
);
}
}
class AboutPage extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('About'),
),
body: Center(
child: Text('About Page'),
),
);
}
}
class ProfilePage extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
final Map<String, dynamic> args = OwletRouter.of(context).arguments;
final int userId = args['userId'];
return Scaffold(
appBar: AppBar(
title: Text('Profile'),
),
body: Center(
child: Text('User ID: $userId'),
),
);
}
}