Flutter如何不借助Scaffold更新BottomNavigationBar索引并保存滚动位置
实现方案
- 首先定义全局可访问的
ValueNotifier统一管理底部导航选中索引,替代原来存放在AppBottomNavigation内部的私有状态:
// 可放在项目公共常量文件或任意全局可访问的位置 final ValueNotifier<int> bottomNavIndex = ValueNotifier(0);
- 改造
AppBottomNavigation组件,通过ValueListenableBuilder监听索引变化,移除内部的_selectedIndex状态,同时在页面跳转的then回调中处理pop后的索引同步:
class AppBottomNavigation extends StatefulWidget { const AppBottomNavigation({Key? key}) : super(key: key); @override _AppBottomNavigationState createState() => _AppBottomNavigationState(); } class _AppBottomNavigationState extends State<AppBottomNavigation> { int _pastIndex = 0 ; final List<Widget> _screens =[ const HomeScreen(), const Scaffold(body: Text("No widget yet")), const Scaffold(body: Text("No widget yet")), const Scaffold(body: Text("No widget yet")), const UserProfile() ]; final List<dynamic> menuItems = [ {'icon': 'assets/icons/home.svg','label': 'Home',}, {'icon': 'assets/icons/box.svg','label': 'Delivery',}, {'icon': 'assets/icons/chat.svg','label': 'Messages',}, {'icon': 'assets/icons/wallet.svg','label': 'Wallet',}, {'icon': 'assets/icons/profile.svg','label': 'Profile',}, ]; void _onItemTapped(int index) { // 点击当前已选中的索引直接返回,避免重复push页面 if (index == bottomNavIndex.value) return; // 记录跳转前的旧索引,pop后恢复 final oldIndex = bottomNavIndex.value; // 更新当前选中索引 bottomNavIndex.value = index; if (index == 4) { if(FirebaseAuth.instance.currentUser != null){ Navigator.push( context, MaterialPageRoute(builder: (context) => const UserProfile()) // pop后自动恢复旧索引 ).then((_) => bottomNavIndex.value = oldIndex); }else{ try { Navigator.push( context, MaterialPageRoute(builder: (context) => const LoginScreen()) ).then((_) => bottomNavIndex.value = oldIndex); } catch (e) { print(e); } } } else { if(index != _pastIndex){ Navigator.push( context, MaterialPageRoute(builder: (context) => _screens[index]) ).then((_) => bottomNavIndex.value = oldIndex); } } setState(() { _pastIndex = index; }); } @override Widget build(BuildContext context) { return ValueListenableBuilder<int>( valueListenable: bottomNavIndex, builder: (context, selectedIndex, child) { return BottomNavigationBar( backgroundColor: Colors.white, showUnselectedLabels: true, unselectedItemColor: Colors.black87, elevation: 32.0, type: BottomNavigationBarType.fixed, selectedLabelStyle: const TextStyle(height: 1.5, fontSize: 12,), unselectedLabelStyle: const TextStyle(height: 1.5, fontSize: 12,), items: menuItems.map((i) { return BottomNavigationBarItem( icon: SvgPicture.asset(i['icon']), activeIcon: SvgPicture.asset(i['icon'], color: primaryColor,), label: i['label'], ); }).toList(), currentIndex: selectedIndex, selectedItemColor: MyEditColor, onTap: _onItemTapped, ); } ); } }
- 原有页面的调用逻辑无需修改,你现在每个子页面单独把
AppBottomNavigation放在自身Scaffold的bottomNavigationBar属性的用法可以保留,不需要把所有页面包在同一个Scaffold里,不会触发你之前遇到的首页加载异常问题。
- 滚动位置自动保留说明:因为你用
Navigator.push把新页面加到导航栈时,原有页面没有被销毁,所以SingleChildScrollView的滚动状态会天然保留,pop回到上一级页面时滚动位置不会丢失。 - 系统返回键兼容:不管是用户点击AppBar的返回按钮还是安卓实体返回键触发pop,都会自动触发
then回调更新底部导航索引,不需要额外加WillPopScope处理。
内容的提问来源于stack exchange,提问作者Mo7ammed 7amad
相关产品推荐
相关产品推荐

