Flutter Go Router Shell Route内页面Drawer覆盖底部导航栏问题
Flutter GoRouter Shell Route内页面Drawer覆盖底部导航栏解决方案
问题背景
使用GoRouter的Shell Route实现底部导航栏时,Shell内部页面的Drawer只能在Shell区域内显示,无法覆盖底部导航栏。尝试过两种方案均失败:
- 将rootNavigatorKey/shellNavigatorKey用于页面Scaffold,触发
Multiple widgets used the same GlobalKey错误 - 路由定义处创建全局ScaffoldKey传入页面,调用openDrawer无反应
解决方案核心思路
把承载Drawer的Scaffold移到根层级(MaterialApp.router的builder中),让Drawer属于根Navigator的上下文,这样就能覆盖整个屏幕(包括底部导航栏)。页面不再单独使用Scaffold,仅保留内容部分,通过全局ScaffoldKey触发Drawer。
具体修改步骤及代码示例
1. 定义全局ScaffoldKey
在路由定义文件中,新增全局的ScaffoldState Key,与rootNavigatorKey同级:
final GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'root'); final GlobalKey<ScaffoldState> rootScaffoldKey = GlobalKey<ScaffoldState>(); // 新增全局ScaffoldKey final shellNavigatorKey = GlobalKey<NavigatorState>(); GoRouter myRouter = router(); GoRouter router({String? initialLocation}) => GoRouter( navigatorKey: rootNavigatorKey, initialLocation: initialLocation ?? Routes.explore, debugLogDiagnostics: true, routes: routesList, );
2. 修改App的根结构,添加全局Scaffold
在MaterialApp.router的builder中添加根Scaffold,将Drawer放在这里:
class App extends StatelessWidget { const App({super.key}); @override Widget build(BuildContext context) { bool isDark = context.select((ProfileSettingsCubit value) => value.state.isDark); return MaterialApp.router( debugShowCheckedModeBanner: false, title: 'title', routerDelegate: myRouter.routerDelegate, routeInformationParser: myRouter.routeInformationParser, routeInformationProvider: myRouter.routeInformationProvider, theme: Themes.lightTheme, darkTheme: Themes.darkTheme, themeMode: isDark ? ThemeMode.dark : ThemeMode.light, builder: (context, child) { $styles = AppStyle( appSize: Size(MediaQuery.of(context).size.width, MediaQuery.of(context).size.height), ); // 根Scaffold,承载全局Drawer return Scaffold( key: rootScaffoldKey, drawer: const SearchDrawer(), body: child, ); }, ); } }
3. 调整BottomNavigationShell结构
确保BottomNavigationShell使用自己的Scaffold承载底部导航栏和页面内容:
class BottomNavigationShell extends StatelessWidget { final Widget child; const BottomNavigationShell({super.key, required this.child}); @override Widget build(BuildContext context) { return Scaffold( body: child, bottomNavigationBar: BottomNavigationBar( // 你的底部导航栏配置 items: const [ BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'), BottomNavigationBarItem(icon: Icon(Icons.explore), label: 'Explore'), ], // 补充当前页面选中逻辑(根据你的路由状态处理) ), ); } }
4. 修改ExploreScreen,移除本地Scaffold
去掉ExploreScreen的Scaffold,仅保留内容部分,触发Drawer时使用全局ScaffoldKey:
class ExploreScreen extends StatefulWidget { const ExploreScreen({super.key}); @override State<ExploreScreen> createState() => _ExploreScreenState(); } class _ExploreScreenState extends State<ExploreScreen> { @override Widget build(BuildContext context) { final isDark = context.select((ProfileSettingsCubit value) => value.state.isDark); // 移除本地Scaffold,直接返回页面内容 return Column( children: [ SizedBoxHeight48, Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ Icon( Icons.search, color: $styles.colors.primary1, size: SizeConfig.screenHeight * 0.04, ), const Spacer(), SizedBoxWidth24, Text( 'Explore', style: isDark ? $styles.text.pop500_18white_22 : $styles.text.pop500_18black_22, ), const Spacer(), Text( 'Filter', style: $styles.text.pop500_16primary1_22, ), SizedBoxWidth4, GestureDetector( onTap: () { // 使用全局Key打开Drawer rootScaffoldKey.currentState?.openDrawer(); }, child: Icon( Icons.filter_list, color: $styles.colors.primary1, size: SizeConfig.screenHeight * 0.03, ), ), ], ), ), SizedBoxHeight24, const ExploreTabs(), SizedBoxHeight8, Divider( color: $styles.colors.tff1Background, ), const ExploreListSection(), ], ); } }
原理说明
根Scaffold位于MaterialApp的最外层,其上下文属于根Navigator,因此它的Drawer会覆盖整个屏幕。而之前的页面Scaffold属于Shell Route的子Navigator,Drawer只能在子Navigator的范围内显示,无法突破底部导航栏的层级。
内容的提问来源于stack exchange,提问作者Mihir Rawal
相关产品推荐
相关产品推荐

