Flutter中BottomNavigationBar返回后选中项不更新问题求助
- 处于Booking页面时,切换到Teams、Highlights或Stats等底部导航页面,按下返回键回到原页面,BottomNavigationBar的选中项未同步更新,仍保持之前页面的选中状态。
- 示例:点击底部导航的Stats项进入Stats页面,按返回键回到Booking页面,但底部导航仍显示Stats为绿色选中状态,与当前页面不匹配。
原HomeView代码
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:sportifan_user/screens/main/bookings.dart'; import 'package:sportifan_user/screens/main/choose_match.dart'; import 'package:sportifan_user/screens/main/create_team.dart'; import 'package:sportifan_user/screens/main/edit_team.dart'; import 'package:sportifan_user/screens/main/highlights.dart'; import 'package:sportifan_user/screens/main/stats.dart'; import 'package:sportifan_user/screens/main/venue_screen.dart'; import 'package:sportifan_user/screens/main/your_teams.dart'; import 'package:sportifan_user/widgets/highlights_widget.dart'; import 'package:sportifan_user/widgets/sidebar.dart'; import 'package:sportifan_user/widgets/top_navigation_bar.dart'; class HomeView extends StatefulWidget { const HomeView({super.key}); @override State<HomeView> createState() => _HomeViewState(); } class _HomeViewState extends State<HomeView> with RouteAware { int currentIndex = 0; final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>(); final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); void onTap(int index) { setState(() { currentIndex = index; }); switch (index) { case 0: navigatorKey.currentState?.pushNamed('/'); break; case 1: navigatorKey.currentState?.pushNamed('/teams'); break; case 2: navigatorKey.currentState?.pushNamed('/highlights'); break; case 3: navigatorKey.currentState?.pushNamed('/stats'); break; } } final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>(); @override void didChangeDependencies() { super.didChangeDependencies(); routeObserver.subscribe(this, ModalRoute.of(context) as PageRoute); } @override void dispose() { routeObserver.unsubscribe(this); super.dispose(); } @override void didPopNext() { _updateCurrentIndex(); } void _updateCurrentIndex() { // Define this method to update currentIndex based on the current route switch (ModalRoute.of(context)?.settings.name) { case '/': setState(() { currentIndex = 0; }); break; case '/teams': setState(() { currentIndex = 1; }); break; case '/highlights': setState(() { currentIndex = 2; }); break; case '/stats': setState(() { currentIndex = 3; }); break; } } @override Widget build(BuildContext context) { TextEditingController teamController = TextEditingController(); TextEditingController uidController = TextEditingController(); TextEditingController controller = TextEditingController(); return WillPopScope( onWillPop: () async { if (navigatorKey.currentState?.canPop() ?? false) { navigatorKey.currentState?.pop(); Future.delayed(Duration.zero, () { _updateCurrentIndex(); // Add this line }); return false; // Prevent the default back button behavior } return true; // Allow the default back button behavior if there's no route to pop }, child: Container( color: Color(0xFF238F20), child: SafeArea( child: Scaffold( key: _scaffoldKey, appBar: PreferredSize( preferredSize: Size.fromHeight(72.0), // Set the height here child: TopNavigationBar( controller: controller, onNotificationIconTapped: () { _scaffoldKey.currentState?.openDrawer(); }, ), ), drawer: SideBar( notificationExists: true, ), body: NotificationListener<PopNavigatorNotification>( onNotification: (notification) { _updateCurrentIndex(); return true; }, child: Navigator( key: navigatorKey, initialRoute: '/', onGenerateRoute: (RouteSettings settings) { WidgetBuilder builder; switch (settings.name) { case '/': builder = (BuildContext _) => BookingView(); break; case '/teams': builder = (BuildContext _) => YourTeams(); break; case '/highlights': builder = (BuildContext _) => HighlightsView(highlightsExists: true); break; case '/stats': builder = (BuildContext _) => StatsView(statsExists: true); break; default: throw Exception('Invalid route: ${settings.name}'); } return MaterialPageRoute( builder: builder, settings: settings); }, ), ), bottomNavigationBar: Theme( data: Theme.of(context).copyWith( canvasColor: Colors.white, primaryColor: Color(0xFF238F20), textTheme: Theme.of(context).textTheme.copyWith( caption: TextStyle(color: Colors.grey), ), bottomNavigationBarTheme: BottomNavigationBarThemeData( backgroundColor: Colors.white, elevation: 0, ), ), child: BottomNavigationBar( currentIndex: currentIndex, onTap: onTap, items: [ BottomNavigationBarItem( icon: Icon( Icons.book, ), label: 'Booking', ), BottomNavigationBarItem( icon: Icon(Icons.people), label: 'Teams', ), BottomNavigationBarItem( icon: Icon(Icons.sports_cricket), label: 'Highlights', ), BottomNavigationBarItem( icon: Icon(Icons.graphic_eq), label: 'Stats', ), ], selectedItemColor: Color(0xFF238F20), // change this to your preferred color unselectedItemColor: Colors.grey, showSelectedLabels: true, showUnselectedLabels: true, ), ), ), ), ), ); } } class PopNavigatorNotification extends Notification {}
问题分析
- 路由监听对象错误:RouteObserver订阅的是HomeView自身的ModalRoute,而非内部嵌套Navigator的路由,因此内部页面的pop操作无法触发HomeView的
didPopNext回调。 - 路由名称获取错误:
_updateCurrentIndex中使用ModalRoute.of(context)获取的是HomeView的路由名称,不是内部Navigator当前显示页面的路由名称,导致无法正确匹配页面。 - 自定义通知未触发:
PopNavigatorNotification没有在内部页面执行pop时发送,因此NotificationListener无法收到更新信号。
修复方案
修复后的代码
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:sportifan_user/screens/main/bookings.dart'; import 'package:sportifan_user/screens/main/your_teams.dart'; import 'package:sportifan_user/screens/main/highlights.dart'; import 'package:sportifan_user/screens/main/stats.dart'; import 'package:sportifan_user/widgets/sidebar.dart'; import 'package:sportifan_user/widgets/top_navigation_bar.dart'; class HomeView extends StatefulWidget { const HomeView({super.key}); @override State<HomeView> createState() => _HomeViewState(); } class _HomeViewState extends State<HomeView> { int currentIndex = 0; final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>(); final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); // 优化:避免重复push相同路由 void onTap(int index) { // 获取当前内部Navigator的路由名称 final currentRouteName = navigatorKey.currentState?.currentRoute?.settings.name; String targetRoute = ''; switch (index) { case 0: targetRoute = '/'; break; case 1: targetRoute = '/teams'; break; case 2: targetRoute = '/highlights'; break; case 3: targetRoute = '/stats'; break; } // 如果当前路由和目标路由相同,不执行push if (currentRouteName != targetRoute) { navigatorKey.currentState?.pushNamed(targetRoute); } setState(() { currentIndex = index; }); } @override void initState() { super.initState(); // 监听内部Navigator的路由变化 navigatorKey.currentState?.addListener(_onRouteChanged); } @override void dispose() { navigatorKey.currentState?.removeListener(_onRouteChanged); super.dispose(); } // 路由变化时更新currentIndex void _onRouteChanged() { final routeName = navigatorKey.currentState?.currentRoute?.settings.name; setState(() { switch (routeName) { case '/': currentIndex = 0; break; case '/teams': currentIndex = 1; break; case '/highlights': currentIndex = 2; break; case '/stats': currentIndex = 3; break; default: currentIndex = 0; } }); } @override Widget build(BuildContext context) { TextEditingController controller = TextEditingController(); return WillPopScope( onWillPop: () async { if (navigatorKey.currentState?.canPop() ?? false) { navigatorKey.currentState?.pop(); return false; } return true; }, child: Container( color: Color(0xFF238F20), child: SafeArea( child: Scaffold( key: _scaffoldKey, appBar: PreferredSize( preferredSize: Size.fromHeight(72.0), child: TopNavigationBar( controller: controller, onNotificationIconTapped: () { _scaffoldKey.currentState?.openDrawer(); }, ), ), drawer: SideBar( notificationExists: true, ), body: Navigator( key: navigatorKey, initialRoute: '/', onGenerateRoute: (RouteSettings settings) { WidgetBuilder builder; switch (settings.name) { case '/': builder = (BuildContext _) => BookingView(); break; case '/teams': builder = (BuildContext _) => YourTeams(); break; case '/highlights': builder = (BuildContext _) => HighlightsView(highlightsExists: true); break; case '/stats': builder = (BuildContext _) => StatsView(statsExists: true); break; default: throw Exception('Invalid route: ${settings.name}'); } return MaterialPageRoute(builder: builder, settings: settings); }, ), bottomNavigationBar: Theme( data: Theme.of(context).copyWith( canvasColor: Colors.white, primaryColor: Color(0xFF238F20), textTheme: Theme.of(context).textTheme.copyWith( caption: TextStyle(color: Colors.grey), ), bottomNavigationBarTheme: BottomNavigationBarThemeData( backgroundColor: Colors.white, elevation: 0, ), ), child: BottomNavigationBar( currentIndex: currentIndex, onTap: onTap, items: [ BottomNavigationBarItem( icon: Icon(Icons.book), label: 'Booking', ), BottomNavigationBarItem( icon: Icon(Icons.people), label: 'Teams', ), BottomNavigationBarItem( icon: Icon(Icons.sports_cricket), label: 'Highlights', ), BottomNavigationBarItem( icon: Icon(Icons.graphic_eq), label: 'Stats', ), ], selectedItemColor: Color(0xFF238F20), unselectedItemColor: Colors.grey, showSelectedLabels: true, showUnselectedLabels: true, ), ), ), ), ), ); } }
关键改动点
- 移除无效的RouteObserver和自定义通知:删除原代码中未生效的
RouteAware相关逻辑和PopNavigatorNotification,简化代码。 - 监听内部Navigator的路由变化:在
initState中给内部Navigator添加addListener,路由变化时触发_onRouteChanged更新currentIndex。 - 正确获取内部路由名称:通过
navigatorKey.currentState?.currentRoute?.settings.name获取当前显示页面的路由,确保匹配正确。 - 优化onTap逻辑:判断当前路由是否与目标路由一致,避免重复push相同页面,提升交互体验。
内容的提问来源于stack exchange,提问作者user23296675
相关产品推荐
相关产品推荐

