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
相关产品推荐
相关产品推荐

