APS Navigator - 应用分页系统
Navigator 2.0 和 Router/Pages 的封装,以简化它们的使用。
该库只是Navigator 2.0和Router/Pages API的一个包装器,试图使它们更易于使用。
基本功能集
我们试图实现什么
- 简单的API
- 易于设置
- 需要学习的“新类类型”数量最少
- 无需扩展(或实现)任何内容
- Web支持(请参阅后续部分中的图片)
- 后退/前进按钮
- 动态URL
- 静态URL
- 从Web历史记录恢复应用程序状态
- 路由堆栈控制
- 在特定位置添加/删除页面
- 一次添加多个页面
- 一次删除一个页面范围
- 处理操作系统事件
- 内部(嵌套)导航器
我们试图不实现什么
- 使用代码生成
- 别误会我。代码生成是一项很棒的技术,可以使代码清晰且编码速度更快——我们社区中有使用它的优秀库。
- 问题是:对于像导航这样“基本”的东西,使用这种程序对我来说似乎不自然。
- 使用强类型参数传递
概述
1 - 创建Navigator并定义路由
final navigator = APSNavigator.from(
routes: {
'/dynamic_url_example{?tab}': DynamicURLPage.route,
'/': ...
},
);
2 - 配置MaterialApp以使用它
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerDelegate: navigator,
routeInformationParser: navigator.parser,
);
}
}
3 - 创建widget Page(路由)
class DynamicURLPage extends StatefulWidget {
final int tabIndex;
const DynamicURLPage({Key? key, required this.tabIndex}) : super(key: key);
@override
_DynamicURLPageState createState() => _DynamicURLPageState();
// Builder function
static Page route(RouteData data) {
final tab = data.values['tab'] == 'books' ? 0 : 1;
return MaterialPage(
key: const ValueKey('DynamicURLPage'), // Important! Always include a key
child: DynamicURLPage(tabIndex: tab),
);
}
}
- 您无需使用静态函数作为PageBuilder,但这似乎是组织事物的不错方式。
- 重要提示:**避免**在
MaterialPage或DynamicURLPage级别使用“const”关键字,否则“Pop”可能无法与Web历史记录正常工作。 - 重要提示:**始终**包含一个Key。
4 - 导航到它
APSNavigator.of(context).push(
path: '/dynamic_url_example',
params: {'tab': 'books'},
);
- 浏览器地址栏将显示:
/dynamic_url_example?tab=books。 - 将创建
Page并将其放在路由堆栈的顶部。
以下各节将更好地描述上述步骤。
用法
1 - 创建Navigator并定义路由
final navigator = APSNavigator.from(
// Defines the initial route - default is '/':
initialRoute: '/dynamic_url_example',
// Defines the initial route params - default is 'const {}':
initialParams: {'tab': '1'},
routes: {
// Defines the location: '/static_url_example'
'/static_url_example': PageBuilder..,
// Defines the location (and queries): '/dynamic_url_example?tab=(tab_value)&other=(other_value)'
// Important: Notice that the '?' is used only once
'/dynamic_url_example{?tab,other}': PageBuilder..,
// Defines the location (and path variables): '/posts' and '/posts/(post_id_value)'
'/posts': PageBuilder..,
'/posts/{post_id}': PageBuilder..,
// Defines the location (with path and query variables): '/path/(id_value)?q1=(q1_value)&q2=(q2_value)'.
'/path/{id}?{?q1,q2}': PageBuilder..,
// Defines app root - default
'/': PageBuilder..,
},
);
routes只是Templates和Page Builders之间的映射。
Templates是简单的字符串,其中包含用于路径({a})和查询({?a,b,c..})值的预定义标记。Page Builders是返回Page并接收RouteData的普通函数。请参阅下面的第3部分。
根据以上配置,应用程序将打开:/dynamic_url_example?tab=1。
2 - 配置MaterialApp
创建Navigator后,我们需要将其设置为已使用。
-
将其设置为
MaterialApp.router.routeDelegate。 -
请记住也要添加
MaterialApp.router.routeInformationParser。class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);@override Widget build(BuildContext context) { return MaterialApp.router( routerDelegate: navigator, routeInformationParser: navigator.parser, ); }}
3 - 创建widget Page(路由)
构建Page时
-
该库会尝试将地址
templates与当前地址进行匹配。例如。- 模板:
/dynamic_url_example/{id}{?tab,other}' - 地址:
/dynamic_url_example/10?tab=1&other=abc
- 模板:
-
所有路径和查询值都将被提取并包含在
RouteData.data实例中。例如。{'id': '10', 'tab': '1', 'other': 'abc'}
-
此实例将作为参数传递给
PageBuilder函数——static Page route(RouteData data)... -
将创建一个新的Page实例并包含在路由堆栈中——您可以通过开发工具轻松检查。
class DynamicURLPage extends StatefulWidget {
final int tabIndex;
const DynamicURLPage({Key? key, required this.tabIndex}) : super(key: key);@override _DynamicURLPageState createState() => _DynamicURLPageState(); // You don't need to use a static function as Builder, // but it seems to be a good way to organize things static Page route(RouteData data) { final tab = data.values['tab'] == 'books' ? 0 : 1; return MaterialPage( key: const ValueKey('DynamicURLPage'), // Important! Always include a key child: DynamicURLPage(tabIndex: tab), ); }}
4 - 导航到Pages
示例链接:所有导航示例
4.1 - 导航到带有查询变量的路由
-
模板:
/dynamic_url_example{?tab,other} -
地址:
/dynamic_url_example?tab=books&other=abcAPSNavigator.of(context).push(
path: '/dynamic_url_example',
params: {'tab': 'books', 'other': 'abc'}, // 在[params]中添加查询值
);
4.2 - 导航到带有路径变量的路由
-
模板:
/posts/{post_id} -
地址:
/posts/10APSNavigator.of(context).push(
path: '/post/10', // 在[path]中设置路径值
);
4.3 - 您也可以包含不作为查询变量使用的参数
-
模板:
/static_url_example -
地址:
/static_url_exampleAPSNavigator.of(context).push(
path: '/static_url_example',
params: {'tab': 'books'}, // 它将被添加到[RouteData.values['tab']]
);
详情
1. 动态URL示例
示例链接:动态URL示例

使用动态URL时,更改应用程序状态也会更改浏览器的URL。为此
-
在模板中包含查询。例如:
/dynamic_url_example{?tab} -
调用
updateParams方法以更新浏览器的URL。final aps = APSNavigator.of(context); aps.updateParams( params: {'tab': index == 0 ? 'books' : 'authors'}, ); -
以上方法将在浏览器历史记录中包含一个新条目。
-
稍后,如果用户选择此类条目,我们可以使用以下方法恢复之前的widget
State。@override void didUpdateWidget(DynamicURLPage oldWidget) { super.didUpdateWidget(oldWidget); final values = APSNavigator.of(context).currentConfig.values; tabIndex = (values['tab'] == 'books') ? 0 : 1; }
重要知识点
- 当前限制:URL中使用的任何值都必须保存为
string。 - 不要忘记为
PageBuilder创建的Page包含一个Key,以使所有内容都能正常工作。
2. 静态URL示例
示例链接:静态URL示例

使用静态URL时,更改应用程序状态不会更改浏览器的URL,但它会生成历史记录中的一个新条目。为此
-
请勿在路由模板中包含查询。例如:
/static_url_example -
就像我们用动态URL所做的一样,再次调用
updateParams方法。final aps = APSNavigator.of(context); aps.updateParams( params: {'tab': index == 0 ? 'books' : 'authors'}, ); -
然后,允许从浏览器历史记录中还原
State。@override void didUpdateWidget(DynamicURLPage oldWidget) { super.didUpdateWidget(oldWidget); final values = APSNavigator.of(context).currentConfig.values; tabIndex = (values['tab'] == 'books') ? 0 : 1; }
重要知识点
- 不要忘记为
PageBuilder创建的Page包含一个Key,以使所有内容都能正常工作。
3. 返回数据示例
示例链接:返回数据示例

推送一个新路由并等待结果。
final selectedOption = await APSNavigator.of(context).push(
path: '/return_data_example',
);
Pop返回数据。
APSNavigator.of(context).pop('Do!');
重要知识点
-
数据将只返回一次。
-
如果用户通过浏览器历史记录导航您的应用程序并再次返回,结果将在
didUpdateWidget方法中以result,的形式返回,而不是通过await调用。@override void didUpdateWidget(HomePage oldWidget) { super.didUpdateWidget(oldWidget); final params = APSNavigator.of(context).currentConfig.values; result = params['result'] as String; if (result != null) _showSnackBar(result!); }
4. 多次推送
示例链接:多次推送示例

一次推送一个Page列表。
APSNavigator.of(context).pushAll(
// position: (default is at top)
list: [
ApsPushParam(path: '/multi_push', params: {'number': 1}),
ApsPushParam(path: '/multi_push', params: {'number': 2}),
ApsPushParam(path: '/multi_push', params: {'number': 3}),
ApsPushParam(path: '/multi_push', params: {'number': 4}),
],
);
在上面的示例中,ApsPushParam(path: '/multi_push', params: {'number': 4}),将是新的顶部。
重要知识点
- 您不必一定将其添加到顶部;您可以使用
position参数将其添加到路由堆栈的中间。 - 不要忘记为
PageBuilder创建的Page包含一个Key,以使所有内容都能正常工作。
5. 多次移除
示例链接:多次移除示例

根据范围删除所有您想要的Page。
APSNavigator.of(context).removeRange(start: 2, end: 5);
6. 内部(嵌套)导航器
示例链接:内部导航器示例

class InternalNavigator extends StatefulWidget {
final String initialRoute;
const InternalNavigator({Key? key, required this.initialRoute})
: super(key: key);
@override
_InternalNavigatorState createState() => _InternalNavigatorState();
}
class _InternalNavigatorState extends State<InternalNavigator> {
late APSNavigator childNavigator = APSNavigator.from(
parentNavigator: navigator,
initialRoute: widget.initialRoute,
initialParams: {'number': 1},
routes: {
'/tab1': Tab1Page.route,
'/tab2': Tab2Page.route,
},
);
@override
void didChangeDependencies() {
super.didChangeDependencies();
childNavigator.interceptBackButton(context);
}
@override
Widget build(BuildContext context) {
return Router(
routerDelegate: childNavigator,
backButtonDispatcher: childNavigator.backButtonDispatcher,
);
}
}
重要知识点
- 当前限制:浏览器URL不会根据内部导航器状态进行更新。