Flutter导航管理插件nav_stack的使用
Flutter导航管理插件nav_stack的使用
nav_stack
一个基于MaterialApp.router(Nav 2.0)的简单但强大的基于路径的路由系统。它支持浏览器/深度链接,并在添加新路由时维护历史堆栈。此外,它还提供了灵活的命令式API来更改路径和修改历史堆栈。
安装
dependencies:
nav_stack: ^0.0.1
导入
import 'package:nav_stack/nav_stack.dart';
基本用法
Hello NavStack
要开始,请将NavStackParser()
和NavStackDelegate()
传递给MaterialApp.router
,并在onGenerateStack
回调中声明所有路由。
runApp(
MaterialApp.router(
routeInformationParser: NavStackParser(),
routerDelegate: NavStackDelegate(
// 声明你的全树页面路由
// PathStack会自动根据当前导航路径决定显示什么
onGenerateStack: (context, nav) => PathStack(
routes: {
["/"]: HomeScreen().toStackRoute(), // 主页
["/messages"]: MessagesScreen().toStackRoute(), // 消息页面
["/profile"]: ProfileScreen().toStackRoute(), // 个人资料页面
},
),
));
});
这段代码可能看起来很简单,但实际上包含了很多功能:
- 这完全绑定到浏览器路径,
- 它还会接收任何平台上的深度链接启动值,
- 它提供了一个
nav
控制器,可以随时轻松更改全局路径, - 你可以从任何地方通过
NavStack.of(context)
查找它, - 所有路由都是持久的,在导航之间保持状态(可选)。
在这个基本形式下,这给你提供了类行为的路由表,使用MaterialApp.onGenerateRoute
构建。
如何工作
NavStack
实际上是两个独立组件的组合。
NavStack
库与MaterialApp.router
通信,并提供读写全局导航路径的API。- 然后是
PathStack
,它解析并根据当前导航路径渲染路由。
PathStack
类似于使用字符串而不是整数作为键的IndexedStack
:
- 它支持许多额外的功能,如嵌套支架、自定义过渡、相对路径和参数。
- 你可以将多个
PathStack
嵌套在一起以轻松形成复杂的路由表。 - 类似于
IndexedStack
,PathStack
的子项可以是持久的。 - 在底层,它实际上只是一个
IndexedStack
和一个Map<String, Widget>
。
有关PathStack
的更多文档,请参阅:https://pub.dev/packages/path_stack
嵌套路由和堆栈
为了在所有子路由周围包裹一个自定义的Scaffold或菜单,可以使用scaffoldBuilder
。一种常见的应用是一个具有持久标签菜单的应用程序:
onGenerateStack: (_, __) => PathStack(
// 使用scaffoldBuilder将所有页面包装在一个状态化的标签菜单中
scaffoldBuilder: (_, stack) => _TabScaffold(["/home", "/profile"], child: stack),
routes: {
["/home"]: LoginScreen().toStackRoute(), // 登录页面
["/profile"]: ProfileScreen().toStackRoute(), // 个人资料页面
},
));
如果你想在特定的一组页面周围包裹一个Scaffold,只需创建另一个PathStack
!
这里我们通过结合两个堆栈来围绕应用程序的/settings/
部分包裹一个内部标签菜单:
onGenerateStack: (_, __) {
return PathStack( // 外部Scaffold
scaffoldBuilder: (_, stack) => OuterTabScaffold(stack),
routes: {
["/login", "/"]: LoginScreen().toStackRoute(), // 登录页面
["/settings/"]: PathStack( // 内部Scaffold
scaffoldBuilder: (_, stack) => InnerTabScaffold(stack),
routes: {
// 这将匹配"/settings/profile"。默认情况下,所有路由相对于其父PathStack。
["profile"]: ProfileScreen().toStackRoute(),
["alerts"]: AlertsScreen().toStackRoute(),
},
).toStackRoute(),
},
);
},
当你在/settings
部分内更改路由时,两个Scaffolds都会保持不变,只有内部区域会动画!Scaffolds也是完全状态化的,因此当路由更改时,你可以拥有动画和其他装饰效果。
路径解析规则
路径路由有一些规则:
- 不带尾随斜杠的路由必须完全匹配:
- 例如,
/details
只匹配/details
,不匹配/details/
,/details/12
或/details/?id=12
- 特殊情况是
/
总是完全匹配
- 例如,
- 带尾随斜杠的路由会接受后缀:
- 例如,
/details/
匹配/details/
,/details/12
,/details/id=12&foo=99
等 - 这允许无限级的嵌套和相对路径
- 例如,
- 如果路由有多条路径,只有第一条会被考虑用于后缀检查
- 例如,
["/details", "/details/"]
需要在任一条路径上进行精确匹配 - 例如,
["/details/", "/details"]
允许在任一条路径上使用后缀
- 例如,
这些规则需要一些时间理解,但结合起来它们允许你表达大多数你能想到的树结构。
定义路径和查询字符串参数
支持基于路径(/billing/88/99
)或查询字符串(/billing/?foo=88&bar=99
)的参数。
一个消费基于路径参数的路由看起来像这样:
// /billing/88/99
["billing/:foo/:bar"]: StackRouteBuilder(
builder: (_, args) => BillingPage(foo: args["foo"], bar: args["bar"])
);
一个使用查询字符串参数的路由看起来像这样:
// /billing/?foo=88&bar=99
["billing/"]: StackRouteBuilder(
builder: (_, args) => BillingPage(id: "${args["foo"]}_${args["bar"]}")
);
如果你想在视图中访问参数并解析它们,可以直接这样做:
NavStack.of(context).args;
有关路径如何解析的更多信息,请查看:https://pub.dev/packages/path_to_regexp。 要尝试不同的路由方案,可以使用此演示:https://path-to-regexp.web.app/
toStackRoute vs StackRouteBuilder
PathStack
的一个要求是每个页面Widget都必须包装在一个StackRouteBuilder()
中。由于这可能会难以阅读,我们添加了一个.toStackRoute()
扩展方法。两者之间的唯一区别是,完整的StackRouteBuilder
允许你直接使用它的builder
方法注入参数到你的视图中。
如果你的视图不需要路径参数,考虑使用扩展方法,因为它们通常更易读:
// 这些调用是相同的
["/login"]: LoginScreen().toStackRoute(),
VS
["/login"]: StackRouteBuilder(builder: (_, __) => LoginScreen()),
Navigator.of()呢?
重要的是,你仍然可以充分利用旧的Navigator.push()
,showDialog
,showBottomSheet
API,只是要注意这些路由不会反映在导航路径中。这对于不需要绑定到浏览器历史的用户流程非常有用。此外,Overlay仍然存在,并且表现得正如预期。
主要的区别现在是,Navigator
主要用于覆盖整个应用的东西,而不是改变应用内的页面。
重要提示: 整个NavStack
存在于单个PageRoute
中。这意味着从NavStack
子项中调用Navigator.of(context).pop()
将被忽略。然而,你仍然可以在对话框、底部面板或通过Navigator.push()
添加的页面中使用.pop()
。
高级用法
除了基本的嵌套和路由,NavStack
还支持高级功能,包括别名、正则表达式和路由守卫。
正则表达式
基于路径参数的一个强大方面是你可以在匹配中附加正则表达式。
- 例如,路径
/user/:foo(\d+)
将匹配/user/12
但不匹配/user/alice
- 如果你不熟悉正则表达式,它们是可选的,并且最好用于高级用例
有关此解析的更多详细信息,请查看PathToRegExp
文档:
https://pub.dev/packages/path_to_regexp
别名
每个路由条目可以有多个路径,使其能够匹配任意路径。例如,我们可以设置一个路由来匹配/home
和/
:
["/home", "/"]: LoginScreen().toStackRoute(),
或者一个接受可选命名参数的路由:
// 匹配"/messages/"和"/messages/99"
["/messages/", "/messages/:messageId"]: StackRouteBuilder(
builder: (_, args) => MessageView(args["messageId"] ?? "")
);
路由守卫
守卫允许你在每个路由的基础上拦截导航事件。主要用于防止未经授权的深链接进入应用程序的部分。通常,你可能会将所有主要页面包装在一个authGuard
中,并留下LoginView
未授权,或者你可能有一个受保护的授权部分,如/admin/
。
要做到这一点,你可以使用StackRouteBuilder.onBeforeEnter
回调来运行你自己的自定义逻辑,并决定是否阻止更改。例如,这个守卫将保护AdminPage
并将重定向到LoginScreen
:
// 你可以使用`buildStackRoute`或`StackRouteBuilder`来添加守卫
["/admin"]: AdminPanel().buildStackRoute(
onBeforeEnter: (_, __) => guardAuthSection()
),
...
bool guardAuthSection(String newRoute) {
if (!appModel.isLoggedIn) NavStack.of(context).redirect("/login");
return appModel.isLoggedIn; // 如果返回false,原始路由将不会被进入。
}
由于守卫只是函数,你可以轻松地跨路由重复使用它们,并且也可以通过嵌套PathStack
组件应用于整个部分。
组合使用
这是一个更完整的示例,展示了嵌套堆栈和一个需要用户登录的整个部分。否则,他们将被重定向到/login
:
bool isLoggedIn = false;
...
onGenerateStack: (context, controller) {
return PathStack(
scaffoldBuilder: (_, stack) => _MyScaffold(stack),
routes: {
["/login", "/"]: LoginScreen().toStackRoute(),
["/in/"]: PathStack(
routes: {
["profile/:profileId"]:
StackRouteBuilder(builder: (_, args) => ProfileScreen(profileId: args["profileId"] ?? "")),
["settings"]: SettingsScreen().toStackRoute(),
},
).toStackRoute(onBeforeEnter: (_) {
if (!isLoggedIn) controller.redirect("/login");
return isLoggedIn; // 如果返回false,该路由将不会被进入。
}),
},
);
},
...
void handleLoginPressed() => NavStack.of(context).path = "/login";
void showProfile() => NavStack.of(context).path = "/in/profile/23"; // 受保护
void showSettings() => NavStack.of(context).path = "/in/settings"; // 受保护
更多关于Flutter导航管理插件nav_stack的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter导航管理插件nav_stack的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
nav_stack
是一个用于 Flutter 的导航管理插件,它提供了一种简单且灵活的方式来管理应用程序的导航栈。使用 nav_stack
,你可以轻松地实现复杂的导航逻辑,例如嵌套导航、深层链接、以及基于状态的路由管理。
以下是如何使用 nav_stack
插件的基本指南:
1. 添加依赖
首先,你需要在 pubspec.yaml
文件中添加 nav_stack
依赖:
dependencies:
flutter:
sdk: flutter
nav_stack: ^0.1.0 # 请检查最新版本
然后运行 flutter pub get
来安装依赖。
2. 基本用法
nav_stack
提供了一个 NavStack
小部件,它可以用来管理导航栈。你可以在 NavStack
中定义多个路由,并根据需要切换它们。
import 'package:flutter/material.dart';
import 'package:nav_stack/nav_stack.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: NavStack(
stackBuilder: (context, controller) {
return StackEntry(
path: '/',
builder: (context) => HomeScreen(),
);
},
),
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home')),
body: Center(
child: ElevatedButton(
onPressed: () {
NavStack.of(context).push('/details');
},
child: Text('Go to Details'),
),
),
);
}
}
class DetailsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Details')),
body: Center(
child: ElevatedButton(
onPressed: () {
NavStack.of(context).pop();
},
child: Text('Go Back'),
),
),
);
}
}
3. 定义路由
你可以在 NavStack
中定义多个路由,并使用 StackEntry
来指定每个路由的路径和对应的页面。
NavStack(
stackBuilder: (context, controller) {
return StackEntry(
path: '/',
builder: (context) => HomeScreen(),
children: [
StackEntry(
path: '/details',
builder: (context) => DetailsScreen(),
),
],
);
},
)
4. 导航操作
你可以使用 NavStack.of(context)
来获取当前的导航控制器,并使用 push
、pop
等方法来进行导航操作。
// 导航到新页面
NavStack.of(context).push('/details');
// 返回上一页
NavStack.of(context).pop();
5. 嵌套导航
nav_stack
支持嵌套导航,你可以在一个 NavStack
中嵌套另一个 NavStack
,从而实现复杂的导航逻辑。
NavStack(
stackBuilder: (context, controller) {
return StackEntry(
path: '/',
builder: (context) => HomeScreen(),
children: [
StackEntry(
path: '/nested',
builder: (context) => NavStack(
stackBuilder: (context, controller) {
return StackEntry(
path: '/nested',
builder: (context) => NestedHomeScreen(),
children: [
StackEntry(
path: '/nested/details',
builder: (context) => NestedDetailsScreen(),
),
],
);
},
),
),
],
);
},
)
6. 深层链接
nav_stack
还支持深层链接,你可以通过路径来直接导航到特定的页面。
NavStack(
initialPath: '/nested/details', // 初始化时直接导航到深层链接页面
stackBuilder: (context, controller) {
return StackEntry(
path: '/',
builder: (context) => HomeScreen(),
children: [
StackEntry(
path: '/nested',
builder: (context) => NavStack(
stackBuilder: (context, controller) {
return StackEntry(
path: '/nested',
builder: (context) => NestedHomeScreen(),
children: [
StackEntry(
path: '/nested/details',
builder: (context) => NestedDetailsScreen(),
),
],
);
},
),
),
],
);
},
)
7. 基于状态的路由管理
nav_stack
允许你基于应用程序的状态来管理路由。你可以使用 StackEntry
的 condition
参数来根据条件动态显示不同的页面。
NavStack(
stackBuilder: (context, controller) {
return StackEntry(
path: '/',
builder: (context) => HomeScreen(),
children: [
StackEntry(
path: '/details',
builder: (context) => DetailsScreen(),
condition: () => someCondition, // 根据条件决定是否显示该页面
),
],
);
},
)