Flutter - 如何使用嵌套路由实现持久侧边栏?

问题描述 投票:0回答:2

我正在尝试在 flutter web 中实现持久菜单(侧边栏)。只要应用程序中的所有页面都直接列为菜单项,我就可以实现。 问题是我无法在“内容区域”中打开嵌套页面(页面未在侧面菜单中列出,但在某个按钮上单击从另一个页面内打开)。

我尝试了很多东西,从 Navigation Rail 到 GoRouter,再到侧边栏的不同包。

我不知道要在这里发布什么代码。

我的项目也用了Getx

无论我拥有什么代码,我都可以在内容区域中打开第一级页面,但嵌套页面作为新页面加载并且侧边栏丢失。

          class HomePage extends StatelessWidget {
        const HomePage({super.key});

        @override
        Widget build(BuildContext context) {
          ///
          final MyMenuController menuController = Get.put(MyMenuController());

          ///
          return Scaffold(
            appBar: AppBar(
              title: const Text('Side Menu Example'),
            ),
            body: Row(
              children: [
                Expanded(
                  child: Container(
                    color: Colors.blue,
                    child: ListView(
                      children: [
                        ListTile(
                          title: const Text('Page 1'),
                          selected: menuController.selectedIndex.value == 0,
                          onTap: () {
                            menuController.selectTab(0);
                          },
                        ),
                        ListTile(
                          title: const Text('Page 2'),
                          selected: menuController.selectedIndex.value == 1,
                          onTap: () {
                            menuController.selectTab(1);
                          },
                        ),
                        ListTile(
                          title: const Text('Page 3'),
                          selected: menuController.selectedIndex.value == 2,
                          onTap: () {
                            menuController.selectTab(2);
                          },
                        ),
                      ],
                    ),
                  ),
                ),
                Expanded(
                  flex: 5,
                  child: Container(color: Colors.white, child: const SizedBox() //

                      Obx(
                        () {
                          switch (menuController.selectedIndex.value) {
                            case 0:
                              return const Page1();
                            case 1:
                              return const Page2();
                            case 2:
                              return const Page3();
                            default:
                              return Container();
                            return Container();
                          }
                        },
                      ),
                      ),
                ),
              ],
            ),
            // ),
          );
        }
      }

以防万一有人想说“告诉我们你试过什么”。到目前为止,我已经尝试了很多东西。如果你知道如何做到这一点,请指出正确的方向。

flutter dart nested sidebar flutter-getx
2个回答
0
投票

先了解

您首先需要了解

Navigator
小部件将所有打开的路线列为“兄弟姐妹”,当您确实有一个
Page1
是导航器中的一条路线时,您可以认为它是
 的孩子Navigator
当您打开该
Navigator
的另一条路线时,它会作为该
Page2
的兄弟姐妹打开。

Navigator 小部件是一个

InheritedWidget
,这意味着使用Getx 进行路由会禁用与
BuildContext
的交互 它(以
Get.to(Widget())
为例),你需要避免/不使用。

              ---> Page1---> Button1 ( that opens Page2 route )
Navigator => |
              ---> Page2

这与您的案子有什么关系?

通过理解这一点,如果您在

Page1
中有一个菜单栏,它不会在
Page2
中显示,因为它是一条不同的路线,这就是为什么您会在您的应用程序中出现这种行为。

如何修复/实现您的预期行为?

Navigator
小部件是一个
InheritedWidget
,这意味着要从您的应用程序访问它,我们使用
Navigator.of(context)
,这会启动默认包含在我们的
Navigator
中的
MaterialApp
小部件应用程序

为了拥有一个始终显示持久导航栏的应用程序,即使我们确实导航到应用程序中的嵌套路由,我们需要创建一个新的

Navigator
小部件,它位于具有菜单栏的屏幕前面。

                                      ---> Page1 ---> Button1 ( that opens Page2 route )
   ScreenWithMenuBar -> Navigator => |
                                      ---> Page2

所以当您在页面之间导航时,总是会显示

ScreenWithMenuBar


0
投票

几天来我一直在使用 GoRouter,并且仍在尝试理解一些关键概念,但这就是我设法实现嵌套导航的方式。

== 路由器 ==

  • 不太喜欢将所有路由放入一个文件(来自 Beamer,您可以在其中为每个屏幕设置一个位置,因此这部分可能需要重构)
  • 魔法发生在
    ShellRoute
    因为它返回
    RootLayout
    我有一个
    AdaptiveNavigator
    并期待一个孩子是你想要显示的屏幕
  • 你基本上有 2 个导航器,因此有 2 个键/状态,每个都处理导航状态和屏幕状态
  • 如果您有不想显示导航器的屏幕,您需要提供
    _parentKey
    parentNavigatorKey
  • 查看最佳实践似乎顶级页面不应该有过渡动画,只有当你导航更深时 => 记录列表将在没有动画的情况下显示,而记录屏幕的详细信息将有
    CupertinoPage
    MaterialPage
     CustomTransitionPage
    在这里你可以添加自己的魔法
final _parentKey = GlobalKey<NavigatorState>();
final _shellKey = GlobalKey<NavigatorState>();

final appRouter = GoRouter(
  // debugLogDiagnostics: true,
  navigatorKey: _parentKey,
  initialLocation: Routes.login,
  redirect: (BuildContext context, GoRouterState state) {
    final bool isAuthenticated =
        context.read<AuthenticationBloc>().isAuthenticated;
    if (state.subloc == Routes.register && !isAuthenticated) {
      return Routes.register;
    } else if (state.subloc == Routes.login && !isAuthenticated) {
      return Routes.login;
    } else if (state.subloc == Routes.login && isAuthenticated) {
      return Routes.dashboard;
    } else if (!isAuthenticated) {
      return Routes.login;
    } else {
      return null;
    }
  },
  // TODO => create page not found screen
  // * this is mainly used for the web version when you hardlink to an unknown page
  errorBuilder: ((context, state) => const Scaffold(
        body: Center(
          child: Text(
            'This my custom errorpage',
            textAlign: TextAlign.center,
          ),
        ),
      )),
  routes: [
    ShellRoute(
      // * => shell route for the navigation bar
      navigatorKey: _shellKey,
      builder: (context, state, child) {
        return RootLayout(child: child);
      },
      routes: [
        GoRoute(
          name: 'dashboard',
          path: Routes.dashboard,
          pageBuilder: (context, state) => NoTransitionPage(
            key: state.pageKey,
            child: const DashboardPage(),
          ),
        ),
        // * => documents route + sub routes
        GoRoute(
          name: 'documents',
          path: Routes.documents,
          pageBuilder: (context, state) => NoTransitionPage(
            key: state.pageKey,
            child: const DocumentsPage(),
          ),
          routes: [
            GoRoute(
              parentNavigatorKey: _parentKey,
              name: 'documentsView',
              path: Routes.documentsView,
              pageBuilder: (context, state) => CupertinoPage(
                key: state.pageKey,
                child: ViewDocumentPage(
                  documentUid: state.params['documentUid'] as String,
                ),
              ),
            ),
            GoRoute(
              parentNavigatorKey: _parentKey,
              name: 'documentsEdit',
              path: Routes.documentsEdit,
              pageBuilder: (context, state) => CupertinoPage(
                key: state.pageKey,
                child: EditDocumentPage(
                  documentUid: state.params['documentUid'] as String,
                ),
              ),
            ),
            GoRoute(
              parentNavigatorKey: _parentKey,
              name: 'documentsAdd',
              path: Routes.documentsAdd,
              pageBuilder: (context, state) => CupertinoPage(
                key: state.pageKey,
                child: const AddDocumentPage(),
              ),
            ),
          ],
        ),
        // * => websites route + sub routes
        GoRoute(
          name: 'websites',
          path: Routes.websites,
          pageBuilder: (context, state) => NoTransitionPage(
            key: state.pageKey,
            child: const WebsitesPage(),
          ),
          routes: [
            GoRoute(
              parentNavigatorKey: _parentKey,
              name: 'websitesAdd',
              path: Routes.websitesAdd,
              pageBuilder: (context, state) => CupertinoPage(
                key: state.pageKey,
                child: const AddWebsitePage(),
              ),
            ),
          ],
        ),
        // * => tasks route + sub routes
        GoRoute(
          name: 'tasks',
          path: Routes.tasks,
          pageBuilder: (context, state) => NoTransitionPage(
            key: state.pageKey,
            child: const ListTasksPage(),
          ),
          routes: [
            GoRoute(
              parentNavigatorKey: _parentKey,
              name: 'tasksAdd',
              path: Routes.tasksAdd,
              pageBuilder: (context, state) => CupertinoPage(
                key: state.pageKey,
                child: const AddTaskPage(),
              ),
            ),
          ],
        ),
      ],
    ),
    // * => Auth
    GoRoute(
      name: 'login',
      parentNavigatorKey: _parentKey,
      path: Routes.login,
      pageBuilder: (context, state) => CupertinoPage(
        key: state.pageKey,
        child: const LoginPage(),
      ),
    ),
    GoRoute(
      name: 'register',
      parentNavigatorKey: _parentKey,
      path: Routes.register,
      pageBuilder: (context, state) => CupertinoPage(
        key: state.pageKey,
        child: const RegisterPage(),
      ),
    ),
    // * => search
    GoRoute(
      name: 'search',
      parentNavigatorKey: _parentKey,
      path: Routes.search,
      pageBuilder: (context, state) => CupertinoPage(
        key: state.pageKey,
        child: const SearchPage(),
      ),
    ),
  ],
);

==根布局==

  • AdaptiveNavigation
    只需检查布局大小,如果是 Desktop,它将显示
    NavigationRail
    或 Mobile
    BottomNavigationBar
  • _Switcher
    只是一个在布局大小变化时提供一些基本动画的类
class RootLayout extends StatelessWidget {
  const RootLayout({
    Key? key,
    required this.child,
  }) : super(key: key);

      final Widget child;
      static const _switcherKey = ValueKey('switcherKey');
    
      @override
      Widget build(BuildContext context) {
        return AdaptiveNavigation(
          child: _Switcher(
            key: _switcherKey,
            child: child,
          ),
        );
      }
    }
    
    class _Switcher extends StatelessWidget {
      final Widget child;
    
      const _Switcher({
        required this.child,
        super.key,
      });
    
      @override
      Widget build(BuildContext context) {
        return UniversalPlatform.isDesktop
        ? child : AnimatedSwitcher(
        key: key,
        duration: const Duration(milliseconds: 200),
        switchInCurve: Curves.easeInOut,
        switchOutCurve: Curves.easeInOut,
        child: child,
        );
      }
    }

总的来说,这里发生的是一个具有 2 个状态的路由器,它有一个带有导航栏的小部件和一个从 GoRouter 向下传递的子小部件。 如果您需要更多详细信息,请告诉我。

© www.soinside.com 2019 - 2024. All rights reserved.