Flutter导航管理插件navi的使用
Flutter导航管理插件navi的使用
如果你喜欢Flutter,你也会喜欢声明式UI和声明式导航。
Navigator 2.0 提供了一个声明式导航API。不幸的是,它过于复杂且难以使用,并且包含大量的样板代码。不仅如此,它还需要维护一个单一状态来管理整个应用程序的导航系统。这不是一个好的架构,并且不适合大型应用。
Navi 帮助你保留所有Navigator 2.0的强大功能,但具有简单易学的API。它帮助你在分割和隔离的域中管理你的导航系统。
请注意,命令式导航API 也作为声明式API之外的一层被支持。
快速示例
要使用库,控制NaviStack
小部件是你需要学习的一切!
下面是一个具有两个页面的应用程序:
/
显示书籍列表/:id
显示一本书。
dependencies:
navi: any
import 'package:flutter/material.dart';
import 'package:navi/navi.dart';
void main() {
runApp(App());
}
class App extends StatelessWidget {
final _informationParser = NaviInformationParser();
final _routerDelegate = NaviRouterDelegate.material(child: BooksStack());
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routeInformationParser: _informationParser,
routerDelegate: _routerDelegate,
);
}
}
// 你的BooksStack小部件状态应该使用NaviRouteMixin以便在路由更改时接收通知
class _BooksStackState extends State<BooksStack> with NaviRouteMixin<BooksStack> {
Book? _selectedBook;
@override
void onNewRoute(NaviRoute unprocessedRoute) {
// 如果路由更改(例如浏览器地址栏或深层链接),将路由转换为你的状态并重建小部件(堆栈)
final bookId = int.tryParse(unprocessedRoute.pathSegmentAt(0) ?? '');
_selectedBook = getBookById(bookId); // 例如从数据库获取
setState(() {});
}
@override
Widget build(BuildContext context) {
// 你可以像使用其他小部件一样在另一个NaviStack下嵌套NaviStack,以创建嵌套路由
// 请参阅“嵌套堆栈”部分
return NaviStack(
pages: (context) => [
NaviPage.material(
key: const ValueKey('Books'),
// 没有路由属性,url默认为'/'
// BooksPagelet是你的小部件,显示书籍列表
child: BooksPagelet(
// 你可以更新BooksStack小部件的状态以进行导航
// 或者你可以在BooksPagelet内部使用context.navi进行导航(请参阅“跳转到新路由”部分)
onSelectBook: (book) => setState(() {
_selectedBook = book;
}),
),
),
if (_selectedBook != null)
NaviPage.material(
key: ValueKey(_selectedBook),
route: NaviRoute(path: ['${_selectedBook!.id}']), // url为'/:id'
// BookPagelet是你的小部件,显示一本书
child: BookPagelet(book: _selectedBook!),
),
],
onPopPage: (context, route, dynamic result) {
// 弹出时更新状态
if (_selectedBook != null) {
setState(() {
_selectedBook = null;
});
}
},
);
}
}
更多示例
- 深度链接 - 路径参数
- 深度链接 - 查询参数
- 登录/登出/注册路由
- 跳过堆栈
- 动态链接
- 嵌套路由 - 底部导航栏 - 不保持状态
- 嵌套路由 - 底部导航栏 - 保持状态
- 嵌套路由 - 选项卡栏 - 不保持状态
- 嵌套路由 - 选项卡栏 - 保持状态
架构层
包名 | 层次 | 计划 | 解释 |
---|---|---|---|
Navi | 代码生成器 | 发布1.0后 | 生成样板代码 |
Navi | 配置器 | 在可能的情况下发布1.0之前,否则在发布1.0之后 | 类似于Angular或Vue的URL映射方法 |
Navi | 命令式API | 发布1.0之前 | 当声明式不需要时有用 |
Navi | 高级声明式API | 进行中 | 简单易用,同时保留Navigator 2.0的强大功能 |
Flutter SDK Navigator 2.0 | 低级声明式API | N/A | 太复杂且难以使用 |
声明式导航
声明式导航类似于声明式UI,因为它允许你通过当前UI状态描述你的导航系统。更新当前UI状态以告诉Navi决定导航到哪里。
只有在以下情况下,声明式导航才是强大的:
- 提供的API足够简单
- 你的应用程序合理地分割成可管理的域(或在此库中的堆栈)
一个例子,声明式导航的优势在于管理一系列页面以完成单个任务(例如多页的注册表单)。在这种情况下,命令式方法通常更困难。
链式页面场景(也称为页面流)只是你可以使用Navi的一种情况。这个库绝对不仅仅是这些。
跳转到新路由
要进行导航,你有两个选择:
- 声明式:更新你的小部件状态(或多个小部件状态)以重建小部件。它将重建所需的堆栈并相应地更新URL。
- 命令式:调用以下方法:
context.navi.to(['products', '1'])
或context.navi.to(['products/1'])
:导航到绝对URL/products/1
。context.navi.relativeTo(['details'])
:导航到相对URL。如果当前URL (context.navi.currentRoute
) 是/products/1
,则目标URL将是/products/1/details
。你可以使用../
来向上移动一个层级。例如,context.navi.relativeTo(['../2/details'])
将在此示例中导航到/products/2/details
。context.navi.pop()
:快捷方式Navigator.of(context).pop()
。context.navi.maybePop()
:快捷方式Navigator.of(context).maybePop()
。context.navi.canPop()
:快捷方式Navigator.of(context).canPop()
。- TODOs:
context.navi.stack(ProductsStackMarker()).to(['2', 'overview'])
:从给定堆栈的当前URL开始导航到相对URL。如果当前URL是my/long/path/to/products/1/details
并且ProductsStack
的URL是my/long/path/to/products
,则目标URL将是my/path/to/products/2/overview
。context.navi.back()
:返回到历史记录中的上一个页面。
嵌套堆栈
因为NaviStack
只是一个普通的小部件,所以你需要像使用其他小部件一样使用这个小部件来构建嵌套堆栈。
例如,你有一个书店,有两个页面:书列表页面和书页面。它们的URL分别是 /books
和 /books/:id
。
在书页面中,你将内容拆分为两个标签:概述和详细信息。它们的URL分别是 /books/:id/overview
和 /books/:id/details
。
在这种情况下,你可以创建两个堆栈:
// 这个堆栈可以是你的RootStack,直接在你的MaterialApp之下。
NaviStack(
pages: (context) => [
NaviPage.material(
route: NaviRoute(path: ['books']),
child: BooksPagelet(),
),
NaviPage.material(
route: NaviRoute(path: ['books', book.id]),
child: BookStack(),
),
],
);
// 在BookStack小部件中,你构建另一个堆栈
NaviStack(
pages: (context) => [
if (pageId == 'overview') NaviPage.material(
route: NaviRoute(path: ['overview']),
child: BookOverviewPagelet(),
),
if (pageId == 'details') NaviPage.material(
route: NaviRoute(path: ['details']),
child: BookDetailsPagelet(),
),
],
);
主要思想是,在嵌套堆栈 BookStack
中,你不需要知道父堆栈的URL(在这个例子中是 RootStack
)。
Navi会帮你合并父堆栈中的当前URL(例如 /books/1
)和嵌套堆栈中的URL(例如 /overview
)以为你生成最终的URL(例如 /books/1/overview
)。
你可以根据需要创建无限深度的嵌套堆栈,每个堆栈只管理它应该知道的URL部分。
它通常与 <a href="https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html"><code>BottomNavigationBar</code></a>
和 <a href="https://api.flutter.dev/flutter/material/TabBar-class.html"><code>TabBar</code></a>
一起使用,但它肯定适用于其他组件和设计。
如果你想在 <a href="https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html"><code>BottomNavigationBar</code></a>
中保持堆栈的状态,你可以使用 <a href="https://api.flutter.dev/flutter/widgets/IndexedStack-class.html"><code>IndexedStack</code></a>
。
如果你想在 <a href="https://api.flutter.dev/flutter/material/TabBar-class.html"><code>TabBar</code></a>
中保持堆栈的状态,你可以使用 <a href="https://api.flutter.dev/flutter/widgets/AutomaticKeepAliveClientMixin-mixin.html"><code>AutomaticKeepAliveClientMixin</code></a>
。
当与选项卡一起使用并保持选项卡状态时,你会在小部件树中保持多个堆栈分支。在这种情况下,请确保将非活动堆栈中的 active: false
设置为非活动状态。只有当前选项卡中的堆栈设置 active: true
。活跃堆栈负责向浏览器地址栏报告正确的最终URL,而非活跃堆栈不报告。
请在 示例 中查看更多。
自定义页面
要使用默认的material和cupertino页面,你可以使用快捷方式:
NaviRouterDelegate.material()
NaviRouterDelegate.cupertino()
NaviPage.material()
NaviPage.cupertino()
要使用自定义页面,使用默认构造函数:
NaviRouterDelegate(rootPage: () => YourCustomPage())
NaviPage(pageBuilder: (key, child) => YourCustomPage())
TODO: 扁平化一堆栈到单个堆栈
FlatNaviStack(
children: [
NaviStack(),
NaviStack(),
// ...
]
)
更多关于Flutter导航管理插件navi的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter导航管理插件navi的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
在Flutter中,navi
并不是一个官方或广泛认可的导航管理插件。通常,Flutter开发者会使用 flutter/material
包中的 Navigator
类来进行导航管理。不过,如果你提到的 navi
是一个第三方库,并且希望了解如何使用类似的导航管理插件,我可以给你一个基于官方 Navigator
的示例,并假设 navi
的使用方式可能与之类似。
使用 Flutter 官方的 Navigator 进行导航管理
以下是一个使用 Navigator
进行页面导航管理的简单示例:
- 创建主页面(Main Screen)
import 'package:flutter/material.dart';
import 'second_screen.dart'; // 导入第二个页面
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Navigation Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Main Screen'),
),
body: Center(
child: ElevatedButton(
child: Text('Go to Second Screen'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
},
),
),
);
}
}
- 创建第二个页面(Second Screen)
import 'package:flutter/material.dart';
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Screen'),
),
body: Center(
child: ElevatedButton(
child: Text('Go back to Main Screen'),
onPressed: () {
Navigator.pop(context);
},
),
),
);
}
}
假设 navi
插件的使用方式
虽然 navi
不是官方的插件,但假设它提供了类似的 API,代码结构可能会是这样的:
// 假设 navi 插件提供了 Navi 类来管理导航
import 'package:navi/navi.dart'; // 假设的包导入路径
import 'second_screen_navi.dart'; // 假设的第二个页面
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Navi Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: NaviProvider( // 假设使用 NaviProvider 包装主页面
child: MyHomePage(),
navigatorKey: GlobalKey<NaviState>(), // 假设需要提供一个 GlobalKey
),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final Navi navi = Navi.of(context); // 假设使用 Navi.of 获取 Navi 实例
return Scaffold(
appBar: AppBar(
title: Text('Main Screen'),
),
body: Center(
child: ElevatedButton(
child: Text('Go to Second Screen'),
onPressed: () {
navi.push( // 假设使用 navi.push 进行导航
MaterialPageRoute(builder: (context) => SecondScreenNavi()),
);
},
),
),
);
}
}
// 假设的第二个页面,使用 navi 插件的特定方式
class SecondScreenNavi extends StatelessWidget {
@override
Widget build(BuildContext context) {
final Navi navi = Navi.of(context);
return Scaffold(
appBar: AppBar(
title: Text('Second Screen'),
),
body: Center(
child: ElevatedButton(
child: Text('Go back to Main Screen'),
onPressed: () {
navi.pop(); // 假设使用 navi.pop 返回上一页面
},
),
),
);
}
}
注意:上述 navi
插件相关的代码是假设性的,并非真实存在的插件 API。在实际开发中,你需要查阅 navi
插件的官方文档来了解其具体的 API 和使用方法。如果 navi
插件确实存在,并且提供了与 Navigator
类似的 API,那么上述假设性的代码结构可能会与真实使用方式相近。