[译]Flutter Favorite之路由包go_router - 高级路由 - 嵌套导航
「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」。
本文翻译自 go_router (gorouter.dev)
水平太烂~ 翻到怀疑人生~ 不吝赐教~
嵌套导航
有的时候,你想基于当前画面的状态选择一个页面,例如,当前选中的标签。这种情况下,你想选择不只是路由导航过来的画面,而也想选择嵌套在画面中的组件,这称做 “嵌套导航”。“嵌套”导航的关键的区别是页面的部分保持相同,没有过渡,例如,AppBar 在导航到 TabBarView
的不同标签时保持相同。
当然,你也可以使用 TabBarView
组件轻松地做到这一点,但是促使嵌套“导航”的是因为位置发生了改变。即用户从一个标签而切换到另一个标签页时会通知地址栏。这可以使用户轻易地捕获 APP中 任意对象的 动态链接 ,实现 深度链接 。
要使用 go_router 来做到嵌套导航,你可以简单地通过不同的路径导航到相同页面,或者用不同的参数导航到相同的路径,这之间的差异决定了页面的不同状态。例如,要实现上面的 TabBarView
页面,需要一个通过变量来改变选中标签的组件:
class FamilyTabsScreen extends StatefulWidget {
final int index;
FamilyTabsScreen({required Family currentFamily, Key? key})
: index = Families.data.indexWhere((f) => f.id == currentFamily.id),
super(key: key) {
assert(index != -1);
}
@override
_FamilyTabsScreenState createState() => _FamilyTabsScreenState();
}
class _FamilyTabsScreenState extends State<FamilyTabsScreen>
with TickerProviderStateMixin {
late final TabController _controller;
@override
void initState() {
super.initState();
_controller = TabController(
length: Families.data.length,
vsync: this,
initialIndex: widget.index,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
void didUpdateWidget(FamilyTabsScreen oldWidget) {
super.didUpdateWidget(oldWidget);
_controller.index = widget.index;
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text(_title(context)),
bottom: TabBar(
controller: _controller,
tabs: [for (final f in Families.data) Tab(text: f.name)],
onTap: (index) => _tap(context, index),
),
),
body: TabBarView(
controller: _controller,
children: [for (final f in Families.data) FamilyView(family: f)],
),
);
void _tap(BuildContext context, int index) =>
context.go('/family/${Families.data[index].id}');
String _title(BuildContext context) =>
(context as Element).findAncestorWidgetOfExactType<MaterialApp>()!.title;
}
FamilyTabsScreen
是有状态的组件,它携带当前选中的 Family 作为一个参数。它使用 Family 列表中该 Family 的下标来设定当前选中的标签。尽管如此,代替切换当前选中的标签到任意用户点击的内容,它都使用导航来获取相应的下标。这是导航的使用改变了地址栏中的地址。并且,标签下标的切换是通过调用 didUpdateWidget
。因为 FamilyTabsScreen
是有状态的组件,组件自身可以改变,但状态会被保持。当(导航)发生时,对 didUpdateWidget
的调用会改变 TabController
的下标来匹配新的导航位置。
要实现此例中的导航部分,我们需要一个路由把位置转化为 FamilyTabsScreen
的实例,该实例使用当前选中的 Family 来参数化:
final _router = GoRouter(
routes: [
GoRoute(
path: '/',
redirect: (_) => '/family/${Families.data[0].id}',
),
GoRoute(
path: '/family/:fid',
builder: (context, state) {
final fid = state.params['fid']!;
final family = Families.data.firstWhere((f) => f.id == fid,
orElse: () => throw Exception('family not found: $fid'));
return FamilyTabsScreen(key: state.pageKey, currentFamily: family);
},
),
],
);
/
路由是对第一个 Family 的重定向。 /family/:fid
是装配的嵌套导航。用匹配的 fid
参数第一次创建 FamilyTabsScreen
的实例来实现。再者,它使用 state.pageKey
向 Flutter 发送信号告知这和之前是相同的画面。 这种组合使路由保持页面独立,并更新浏览器的地址栏,使 TabBarView
导航到选中的部分。
看起来内容很多,概括起来,需要对页面构建器创建的页面做两件事来支持嵌套导航:
- 使用
StatefulWidget
作为界面组件的基类。 - 当用户导航时,会创建相同的
StatefulWidget
- 派生的类型,传递新的数据,例如,当前选中了哪一个标签。因为正在使用相同 key 的组件,Flutter 会保持状态,但是会使用新数据作为构造参数来包装组件。当新的组件包装器就位时,Flutter 会调用didUpdateWidget
,使你可以使用新数据来更新已存在的组件,例如,选中的标签。
本示例展现了TabBarView
选中的标签,但是你可以用于应用导航的页面中的任意嵌套内容。
保持状态
进行嵌套导航时,用户期望导航到一个新页面再返回时,组件保持相同的状态,例如,滚动位置,输入的文本内容,等。可以对有状态的组件使用 AutomaticKeepAliveClientMixin
来支持该要求。你可以在 nested_nav.dart
示例的 FamilyView
中看到此操作:
class FamilyView extends StatefulWidget {
const FamilyView({required this.family, Key? key}) : super(key: key);
final Family family;
@override
State<FamilyView> createState() => _FamilyViewState();
}
/// Use the [AutomaticKeepAliveClientMixin] to keep the state.
class _FamilyViewState extends State<FamilyView>
with AutomaticKeepAliveClientMixin {
// Override `wantKeepAlive` when using `AutomaticKeepAliveClientMixin`.
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
// Call `super.build` when using `AutomaticKeepAliveClientMixin`.
super.build(context);
return ListView(
children: [
for (final p in widget.family.people)
ListTile(
title: Text(p.name),
onTap: () =>
context.go('/family/${widget.family.id}/person/${p.id}'),
),
],
);
}
}
要指示 AutomaticKeepAliveClientMixin
来保持状态,你需要覆写 wantKeepAlive
返回 true
,然后 在 State
类的 build
方法中调用 super.build
。如上所示。
注意,在滚动到 Hunting Family 长列表的底部之后,切换到其它标签,然后跳转到一个新页面,当返回到 Hunting 列表时,滚动位置是保持的。
转载自:https://juejin.cn/post/7054706061365739550