我正在努力实现一个持久的菜单(侧栏)在扑动网。我可以实现,只要在应用程序中的所有页面都直接列为菜单项。问题是我无法获得嵌套页面(未在侧边菜单中列出的页面,但从另一个页面中单击某些按钮打开)在"内容区"中打开。
我已经尝试了很多东西,从导航轨道到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();
}
},
),
),
),
],
),
// ),
);
}
}
以防有人想说"让我们看看你试过什么"。我尝试了很多方法,但到目前为止都没有效果。如果你知道如何做到这一点,请给我指出正确的方向。
先知道
您首先需要了解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
。
我使用GoRouter几天了,仍然试图理解一些关键概念,但这就是我如何实现嵌套导航的。
== Router ==
- 不太喜欢把所有的路由放到一个文件中(来自Beamer,你需要为每个屏幕设置一个位置,所以这部分可能需要重构)
- 魔法发生在
ShellRoute
,因为它返回RootLayout
,我有一个AdaptiveNavigator
,并期望一个子屏幕,你想要显示 - 你基本上有两个导航器因此有两个键/state每个键处理导航状态和屏幕状态
- 如果你不希望在屏幕上显示导航器,你需要提供
_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(),
),
),
],
);
== RootLayout ==
AdaptiveNavigation
只是检查布局大小,如果桌面,它将显示NavigationRail
或移动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,
);
}
}
总的来说,这里发生的是一个有两个状态的路由器,它有一个带导航条的小部件和一个从GoRouter传递下来的子部件作为根。如果您需要进一步的细节,请告诉我。