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

Flutter中GoRouter实现带安卓返回键的持久化底部导航问题

问题描述

我用Flutter的GoRouter结合StatefulShellRoute实现持久化底部导航栏,代码写完后达不到预期效果:

  • 预期:点击安卓返回键时回到上一个切换过的标签页
  • 实际:点击返回键直接关闭应用

路由类代码

class AppNavigation {
  AppNavigation._();

  static final _rootNavigatorKey = GlobalKey<NavigatorState>();
  static final GlobalKey<NavigatorState> _homeTabNavigatorKey =
      GlobalKey<NavigatorState>();
  static final GlobalKey<NavigatorState> _settingsTabNavigatorKey =
      GlobalKey<NavigatorState>();
  static final GlobalKey<NavigatorState> _profileTabNavigatorKey =
      GlobalKey<NavigatorState>();
  static final router = GoRouter(
    navigatorKey: _rootNavigatorKey,
    initialLocation: "/home",
    debugLogDiagnostics: true,
    routes: [
      GoRoute(
        path: SplashScreen.routePath,
        name: SplashScreen.routeName,
        pageBuilder: (context, state) => getPage(
          child: const SplashScreen(),
          state: state,
        ),
      ),
      StatefulShellRoute.indexedStack(
        parentNavigatorKey: _rootNavigatorKey,
        pageBuilder: (
          BuildContext context,
          GoRouterState state,
          StatefulNavigationShell navigationShell,
        ) {
          return getPage(
            child: BottomNavigationScreen(
              child: navigationShell,
            ),
            state: state,
          );
        },
        branches: [
          StatefulShellBranch(
            navigatorKey: _homeTabNavigatorKey,
            routes: [
              GoRoute(
                path: "/home",
                pageBuilder: (context, GoRouterState state) {
                  return getPage(
                    child: const HomeScreen(),
                    state: state,
                  );
                },
              ),
            ],
          ),
          StatefulShellBranch(
            navigatorKey: _settingsTabNavigatorKey,
            routes: [
              GoRoute(
                path: "/settings",
                pageBuilder: (context, GoRouterState state) {
                  return getPage(
                    child: const SettingsScreen(),
                    state: state,
                  );
                },
              ),
            ],
          ),
          StatefulShellBranch(
            navigatorKey: _profileTabNavigatorKey,
            routes: [
              GoRoute(
                path: "/profile",
                pageBuilder: (context, GoRouterState state) {
                  return getPage(
                    child: const ProfileScreen(),
                    state: state,
                  );
                },
              ),
            ],
          )
        ],
      )
    ],
  );

  static Page getPage({
    required Widget child,
    required GoRouterState state,
  }) {
    return MaterialPage(
      key: state.pageKey,
      child: child,
    );
  }
}

底部导航页面代码

class BottomNavigationScreen extends StatefulWidget {
  const BottomNavigationScreen({super.key, required this.child});
  final StatefulNavigationShell child;
  @override
  State<BottomNavigationScreen> createState() => _BottomNavigationScreenState();
}

class _BottomNavigationScreenState extends State<BottomNavigationScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Bottom Navigator Shell'),
      ),
      body: SafeArea(
        child: widget.child,
      ),
      bottomNavigationBar: BottomNavigationBar(
        type: BottomNavigationBarType.fixed,
        currentIndex: widget.child.currentIndex,
        onTap: (index) {
          widget.child.goBranch(
            index,
            initialLocation: index == widget.child.currentIndex,
          );

          setState(() {});
        },
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: 'Profile',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            label: 'Settings',
          ),
        ],
      ),
    );
  }
}
解决方案

问题核心是未拦截安卓返回键事件,也未维护标签页切换历史。做以下两处修改即可解决:

1. 拦截返回键+维护标签页历史

修改_BottomNavigationScreenState,添加返回键处理逻辑和历史记录:

class _BottomNavigationScreenState extends State<BottomNavigationScreen> {
  final List<int> _tabHistory = [0]; // 初始默认选中首页

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: _handleBackPress,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Bottom Navigator Shell'),
        ),
        body: SafeArea(child: widget.child),
        bottomNavigationBar: BottomNavigationBar(
          type: BottomNavigationBarType.fixed,
          currentIndex: widget.child.currentIndex,
          onTap: (index) {
            // 更新历史:避免重复记录当前页
            if (widget.child.currentIndex != index) {
              _tabHistory.remove(index);
              _tabHistory.add(index);
            }
            widget.child.goBranch(
              index,
              initialLocation: index == widget.child.currentIndex,
            );
            setState(() {});
          },
          items: const [
            BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
            BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
            BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Settings'),
          ],
        ),
      ),
    );
  }

  // 自定义返回键逻辑
  Future<bool> _handleBackPress() async {
    // 先判断当前标签页内是否有可返回的子页面
    final currentBranchNavigator = widget.child.navigatorForKey(widget.child.currentIndex);
    if (currentBranchNavigator?.canPop() == true) {
      currentBranchNavigator!.pop();
      return false;
    }

    // 回到上一个标签页
    if (_tabHistory.length > 1) {
      _tabHistory.removeLast();
      final previousIndex = _tabHistory.last;
      widget.child.goBranch(previousIndex);
      setState(() {});
      return false;
    }

    // 只有回到首页且无历史时,才允许退出应用
    return true;
  }
}

2. 优化GoRouter返回行为(可选)

在GoRouter初始化时添加popBehavior配置,避免默认返回逻辑干扰:

static final router = GoRouter(
  navigatorKey: _rootNavigatorKey,
  initialLocation: "/home",
  debugLogDiagnostics: true,
  popBehavior: PopBehavior.prevent, // 阻止默认pop,交由自定义逻辑处理
  routes: [
    // ... 原有路由配置
  ],
);

逻辑说明

  • WillPopScope拦截安卓返回键,优先处理标签页内的子页面返回
  • _tabHistory记录标签页切换顺序,确保返回时回到上一个访问的标签页
  • 只有当回到首页且无历史记录时,才允许退出应用

内容的提问来源于stack exchange,提问作者Miraj Hossain Shawon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 22:18:15