You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 13:34:57