likes
comments
collection
share

[译]Flutter Favorite之路由包go_router - 高级路由 - 嵌套导航

作者站长头像
站长
· 阅读数 199

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」。

本文翻译自 go_router (gorouter.dev)

水平太烂~ 翻到怀疑人生~ 不吝赐教~


嵌套导航

有的时候,你想基于当前画面的状态选择一个页面,例如,当前选中的标签。这种情况下,你想选择不只是路由导航过来的画面,而也想选择嵌套在画面中的组件,这称做 “嵌套导航”。“嵌套”导航的关键的区别是页面的部分保持相同,没有过渡,例如,AppBar 在导航到 TabBarView 的不同标签时保持相同。

[译]Flutter Favorite之路由包go_router - 高级路由 - 嵌套导航

当然,你也可以使用 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 导航到选中的部分。

看起来内容很多,概括起来,需要对页面构建器创建的页面做两件事来支持嵌套导航:

  1. 使用 StatefulWidget 作为界面组件的基类。
  2. 当用户导航时,会创建相同的 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 。如上所示。

[译]Flutter Favorite之路由包go_router - 高级路由 - 嵌套导航

注意,在滚动到 Hunting Family 长列表的底部之后,切换到其它标签,然后跳转到一个新页面,当返回到 Hunting 列表时,滚动位置是保持的。


转载自:https://juejin.cn/post/7054706061365739550
评论
请登录