如何在同一层次结构路由中将 Shell Route 与 GoRoute 一起使用

김상현*_*김상현 23 android routes ios dart flutter

\n

如何在同一层次结构路由中将 Shell Route 与 GoRoute 一起使用

\n\n

如何使用导航栏按钮之一从 ShellRoute 推送到与 ShellRoute 同一层次结构中的 GoRoute?

\n

目前GoRouter的路由有两个值:ShellRoute和GoRoute。ShellRoute 中的 Child 代表 Home、Discover 和 Shop Widget。然而,除了上面提到的 Widget 的三个按钮之外,NavBar 还多了一个第四个按钮。这个按钮(MY)用于转到前面提到的 GoRoute。

\n

正如您从下面附加的代码中看到的,我希望在 ShellRoute 上按下第四个按钮时显示登录小部件,因为 GoRoute 堆叠在 ShellRoute 上。但实际上,随着 ShellRoute 被删除,GoRoute 成为顶部堆栈,iOS 没有后退按钮,Android 在 BackPress 时关闭应用程序。

\n

官方文档examples中没有这方面的例子,所以我迷失了很多。\n我发现了类似的例子一万个\n https://github.com/flutter/packages/blob/main/packages/go_router/示例/lib/shell_route.dart \n在上面的示例中,GoRoute 依赖于 ShellRoute。\n我不想要这个。我想让登录路线可以从任何地方访问。

\n

我将在下面附上我的代码和操作屏幕。\n我需要你的帮助。

\n

预期成绩: \n当按下第四个按钮时,GoRoute 堆叠在 ShellRoute 上,按下背面时 ShellRoute 重新出现

\n

实际结果: \n当按下第四个按钮时,ShellRoute 被移除,创建一个 GoRoute 并将其堆叠在顶部,当您按 Back 时应用程序结束。

\n

\xe1\x84\x92\xe1\x85\xaa\xe1\x84\x86\xe1\x85\xa7\xe1\x86\xab \xe1\x84\x80\xe1\x85\xb5\xe1\x84\x85\xe1 \x85\xa9\xe1\x86\xa8 2022-11-30 \xe1\x84\x8b\xe1\x85\xa9\xe1\x84\x92\xe1\x85\xae 4 21 12 mov

\n
Performing hot restart...\nSyncing files to device iPhone 12...\nRestarted application in 368ms.\nflutter: MyTest didPush: _PageBasedMaterialPageRoute<void>(MaterialPage<void>("/", [<\'280748962\'>], {}), animation: AnimationController#0adb5(\xe2\x8f\xad 1.000; paused; for _PageBasedMaterialPageRoute<void>(/)))\nflutter: MyTest didPush: _PageBasedMaterialPageRoute<void>(MaterialPage<void>("/login", [<\'621841910\'>], {}), animation: AnimationController#4775c(\xe2\x96\xb6 0.000; for _PageBasedMaterialPageRoute<void>(/login)))\nflutter: MyTest didRemove: _PageBasedMaterialPageRoute<void>(MaterialPage<void>("/shop", [<\'280748962\'>], {}), animation: AnimationController#0adb5(\xe2\x8f\xad 1.000; paused; for _PageBasedMaterialPageRoute<void>(/)))\n
Run Code Online (Sandbox Code Playgroud)\n\n代码示例\n\n

主程序.dart

\n
Performing hot restart...\nSyncing files to device iPhone 12...\nRestarted application in 368ms.\nflutter: MyTest didPush: _PageBasedMaterialPageRoute<void>(MaterialPage<void>("/", [<\'280748962\'>], {}), animation: AnimationController#0adb5(\xe2\x8f\xad 1.000; paused; for _PageBasedMaterialPageRoute<void>(/)))\nflutter: MyTest didPush: _PageBasedMaterialPageRoute<void>(MaterialPage<void>("/login", [<\'621841910\'>], {}), animation: AnimationController#4775c(\xe2\x96\xb6 0.000; for _PageBasedMaterialPageRoute<void>(/login)))\nflutter: MyTest didRemove: _PageBasedMaterialPageRoute<void>(MaterialPage<void>("/shop", [<\'280748962\'>], {}), animation: AnimationController#0adb5(\xe2\x8f\xad 1.000; paused; for _PageBasedMaterialPageRoute<void>(/)))\n
Run Code Online (Sandbox Code Playgroud)\n

scaffold_with_nav_bar.dart

\n
void main() {\n  runApp(const MyApp());\n}\n\nfinal _rootNavigatorKey = GlobalKey<NavigatorState>();\nfinal _shellNavigatorKey = GlobalKey<NavigatorState>();\n\nfinal _router = GoRouter(\n  initialLocation: \'/\',\n  navigatorKey: _rootNavigatorKey,\n  observers: [\n    GoRouterObserver(),\n  ],\n  routes: [\n    ShellRoute(\n        navigatorKey: _shellNavigatorKey,\n        builder: (context, state, child) {\n          return ScaffoldWithNavBar(child: child);\n        },\n        routes: [\n          GoRoute(\n            path: \'/\',\n            builder: (context, state) {\n              return const Home();\n            },\n          ),\n          GoRoute(\n            path: \'/discover\',\n            builder: (context, state) {\n              return const Discover();\n            },\n          ),\n          GoRoute(\n              path: \'/shop\',\n              builder: (context, state) {\n                return const Shop();\n              }),\n        ],\n    ),\n    GoRoute(\n      path: \'/login\',\n      builder: (context, state) {\n        return const Login();\n      },\n    ),\n  ],\n);\n\nclass GoRouterObserver extends NavigatorObserver {\n  @override\n  void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {\n    print(\'MyTest didPush: $route\');\n  }\n\n  @override\n  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {\n    print(\'MyTest didPop: $route\');\n  }\n\n  @override\n  void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) {\n    print(\'MyTest didRemove: $route\');\n  }\n\n  @override\n  void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {\n    print(\'MyTest didReplace: $newRoute\');\n  }\n}\n\nclass MyApp extends StatelessWidget {\n  const MyApp({super.key});\n\n  // This widget is the root of your application.\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp.router(\n      title: \'Platform\',\n      theme: ThemeData(\n        primarySwatch: Colors.blue,\n      ),\n      routerConfig: _router,\n    );\n  }\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n\n\n 日志\n\n\n
 info \xe2\x80\xa2 Avoid `print` calls in production code \xe2\x80\xa2 lib/main.dart:62:5 \xe2\x80\xa2 avoid_print\n   info \xe2\x80\xa2 Avoid `print` calls in production code \xe2\x80\xa2 lib/main.dart:67:5 \xe2\x80\xa2 avoid_print\n   info \xe2\x80\xa2 Avoid `print` calls in production code \xe2\x80\xa2 lib/main.dart:72:5 \xe2\x80\xa2 avoid_print\n   info \xe2\x80\xa2 Avoid `print` calls in production code \xe2\x80\xa2 lib/main.dart:77:5 \xe2\x80\xa2 avoid_print\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/discover/discover.dart:11:16 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Avoid using private types in public APIs \xe2\x80\xa2 lib/sections/exhibition/detail_exhibition.dart:10:3 \xe2\x80\xa2 library_private_types_in_public_api\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/exhibition/exhibition.dart:11:16 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Unused import: \'package:knowd_platform/sections/discover/discover.dart\' \xe2\x80\xa2 lib/sections/home/home.dart:2:8 \xe2\x80\xa2 unused_import\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/home/home.dart:21:15 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/login/login.dart:11:16 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/login/login_router.dart:8:34 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/login/singup.dart:11:16 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Avoid using private types in public APIs \xe2\x80\xa2 lib/sections/main_tab_bar.dart:12:3 \xe2\x80\xa2 library_private_types_in_public_api\n   info \xe2\x80\xa2 Private field could be final \xe2\x80\xa2 lib/sections/main_tab_bar.dart:17:16 \xe2\x80\xa2 prefer_final_fields\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:18:5 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:19:5 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:20:5 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:21:5 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:41:18 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const literals as parameters of constructors on @immutable classes \xe2\x80\xa2 lib/sections/main_tab_bar.dart:59:17 \xe2\x80\xa2 prefer_const_literals_to_create_immutables\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:60:13 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:61:21 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:64:13 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:65:21 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:68:13 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:69:21 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:72:13 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:73:21 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/shop.dart:10:16 \xe2\x80\xa2 prefer_const_constructors\n
Run Code Online (Sandbox Code Playgroud)\n\n
 info \xe2\x80\xa2 Avoid `print` calls in production code \xe2\x80\xa2 lib/main.dart:62:5 \xe2\x80\xa2 avoid_print\n   info \xe2\x80\xa2 Avoid `print` calls in production code \xe2\x80\xa2 lib/main.dart:67:5 \xe2\x80\xa2 avoid_print\n   info \xe2\x80\xa2 Avoid `print` calls in production code \xe2\x80\xa2 lib/main.dart:72:5 \xe2\x80\xa2 avoid_print\n   info \xe2\x80\xa2 Avoid `print` calls in production code \xe2\x80\xa2 lib/main.dart:77:5 \xe2\x80\xa2 avoid_print\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/discover/discover.dart:11:16 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Avoid using private types in public APIs \xe2\x80\xa2 lib/sections/exhibition/detail_exhibition.dart:10:3 \xe2\x80\xa2 library_private_types_in_public_api\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/exhibition/exhibition.dart:11:16 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Unused import: \'package:knowd_platform/sections/discover/discover.dart\' \xe2\x80\xa2 lib/sections/home/home.dart:2:8 \xe2\x80\xa2 unused_import\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/home/home.dart:21:15 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/login/login.dart:11:16 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/login/login_router.dart:8:34 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/login/singup.dart:11:16 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Avoid using private types in public APIs \xe2\x80\xa2 lib/sections/main_tab_bar.dart:12:3 \xe2\x80\xa2 library_private_types_in_public_api\n   info \xe2\x80\xa2 Private field could be final \xe2\x80\xa2 lib/sections/main_tab_bar.dart:17:16 \xe2\x80\xa2 prefer_final_fields\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:18:5 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:19:5 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:20:5 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:21:5 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:41:18 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const literals as parameters of constructors on @immutable classes \xe2\x80\xa2 lib/sections/main_tab_bar.dart:59:17 \xe2\x80\xa2 prefer_const_literals_to_create_immutables\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:60:13 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:61:21 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:64:13 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:65:21 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:68:13 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:69:21 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:72:13 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/main_tab_bar.dart:73:21 \xe2\x80\xa2 prefer_const_constructors\n   info \xe2\x80\xa2 Prefer const with constant constructors \xe2\x80\xa2 lib/sections/shop.dart:10:16 \xe2\x80\xa2 prefer_const_constructors\n\n
Run Code Online (Sandbox Code Playgroud)\n\n\n

kri*_*yaa 34

使用context.go()fromShellRoute到时要考虑的事项GoRoute

  1. 在每个中指定parentNavigatorKeypropGoRoute
    • 如果页面是以下的子页面ShellRouteparentNavigatorKey:_shellNavigatorKey
    • 如果页面是以下的子页面MainRouteparentNavigatorKey:_rootNavigatorKey
  2. 用于context.go()替换页面,context.push()将页面压入堆栈

代码结构如下:


final _parentKey = GlobalKey<NavigatorState>();
final _shellKey = GlobalKey<NavigatorState>();

|_ GoRoute
  |_ parentNavigatorKey = _parentKey    Specify key here
|_ ShellRoute
  |_ GoRoute                            // Needs Bottom Navigation                  
    |_ parentNavigatorKey = _shellKey   
  |_ GoRoute                            // Needs Bottom Navigation
    |_ parentNavigatorKey = _shellKey   
|_ GoRoute                              // Full Screen which doesn't need Bottom Navigation
  |_parentNavigatorKey = _parentKey
Run Code Online (Sandbox Code Playgroud)

您的代码中的错误

  1. 未指定parentNavigatorKey

  2. 为了backButton可见,你必须有appBar内在Scaffold

  3. 这段代码

    if (index == 3) {
     router.push(location);
     }
     setState(() {
     _currentIndex = index;
     router.go(location);
     });
    }  
    
    Run Code Online (Sandbox Code Playgroud)

代码改进

  1. 可见后退按钮
  2. 导航回 ShellRoute
  3. Navigation单击后退按钮时保留菜单
  4. 修复了使用时路线之间奇怪的转换navbar

代码:

路由器


final _rootNavigatorKey = GlobalKey<NavigatorState>();
final _shellNavigatorKey = GlobalKey<NavigatorState>();

final router = GoRouter(
  initialLocation: '/',
  navigatorKey: _rootNavigatorKey,
  routes: [
    ShellRoute(
      navigatorKey: _shellNavigatorKey,
      pageBuilder: (context, state, child) {
        print(state.location);
        return NoTransitionPage(
            child: ScaffoldWithNavBar(
          location: state.location,
          child: child,
        ));
      },
      routes: [
        GoRoute(
          path: '/',
          parentNavigatorKey: _shellNavigatorKey,
          pageBuilder: (context, state) {
            return const NoTransitionPage(
              child: Scaffold(
                body: Center(child: Text("Home")),
              ),
            );
          },
        ),
        GoRoute(
          path: '/discover',
          parentNavigatorKey: _shellNavigatorKey,
          pageBuilder: (context, state) {
            return const NoTransitionPage(
              child: Scaffold(
                body: Center(child: Text("Discover")),
              ),
            );
          },
        ),
        GoRoute(
            parentNavigatorKey: _shellNavigatorKey,
            path: '/shop',
            pageBuilder: (context, state) {
              return const NoTransitionPage(
                child: Scaffold(
                  body: Center(child: Text("Shop")),
                ),
              );
            }),
      ],
    ),
    GoRoute(
      parentNavigatorKey: _rootNavigatorKey,
      path: '/login',
      pageBuilder: (context, state) {
        return NoTransitionPage(
          key: UniqueKey(),
          child: Scaffold(
            appBar: AppBar(),
            body: const Center(
              child: Text("Login"),
            ),
          ),
        );
      },
    ),
  ],
);
Run Code Online (Sandbox Code Playgroud)

底部导航栏

class ScaffoldWithNavBar extends StatefulWidget {
  String location;
  ScaffoldWithNavBar({super.key, required this.child, required this.location});

  final Widget child;

  @override
  State<ScaffoldWithNavBar> createState() => _ScaffoldWithNavBarState();
}

class _ScaffoldWithNavBarState extends State<ScaffoldWithNavBar> {
  int _currentIndex = 0;

  static const List<MyCustomBottomNavBarItem> tabs = [
    MyCustomBottomNavBarItem(
      icon: Icon(Icons.home),
      activeIcon: Icon(Icons.home),
      label: 'HOME',
      initialLocation: '/',
    ),
    MyCustomBottomNavBarItem(
      icon: Icon(Icons.explore_outlined),
      activeIcon: Icon(Icons.explore),
      label: 'DISCOVER',
      initialLocation: '/discover',
    ),
    MyCustomBottomNavBarItem(
      icon: Icon(Icons.storefront_outlined),
      activeIcon: Icon(Icons.storefront),
      label: 'SHOP',
      initialLocation: '/shop',
    ),
    MyCustomBottomNavBarItem(
      icon: Icon(Icons.account_circle_outlined),
      activeIcon: Icon(Icons.account_circle),
      label: 'MY',
      initialLocation: '/login',
    ),
  ];

  @override
  Widget build(BuildContext context) {
    const labelStyle = TextStyle(fontFamily: 'Roboto');
    return Scaffold(
      body: SafeArea(child: widget.child),
      bottomNavigationBar: BottomNavigationBar(
        selectedLabelStyle: labelStyle,
        unselectedLabelStyle: labelStyle,
        selectedItemColor: const Color(0xFF434343),
        selectedFontSize: 12,
        unselectedItemColor: const Color(0xFF838383),
        showUnselectedLabels: true,
        type: BottomNavigationBarType.fixed,
        onTap: (int index) {
          _goOtherTab(context, index);
        },
        currentIndex: widget.location == '/'
            ? 0
            : widget.location == '/discover'
                ? 1
                : widget.location == '/shop'
                    ? 2
                    : 3,
        items: tabs,
      ),
    );
  }

  void _goOtherTab(BuildContext context, int index) {
    if (index == _currentIndex) return;
    GoRouter router = GoRouter.of(context);
    String location = tabs[index].initialLocation;

    setState(() {
      _currentIndex = index;
    });
    if (index == 3) {
      context.push('/login');
    } else {
      router.go(location);
    }
  }
}

class MyCustomBottomNavBarItem extends BottomNavigationBarItem {
  final String initialLocation;

  const MyCustomBottomNavBarItem(
      {required this.initialLocation,
      required Widget icon,
      String? label,
      Widget? activeIcon})
      : super(icon: icon, label: label, activeIcon: activeIcon ?? icon);
}

Run Code Online (Sandbox Code Playgroud)

输出:

在此输入图像描述

在此输入图像描述