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

Flutter如何不借助Scaffold更新BottomNavigationBar索引并保存滚动位置

实现方案

  1. 首先定义全局可访问的ValueNotifier统一管理底部导航选中索引,替代原来存放在AppBottomNavigation内部的私有状态:
// 可放在项目公共常量文件或任意全局可访问的位置
final ValueNotifier<int> bottomNavIndex = ValueNotifier(0);
  1. 改造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,
        );
      }
    );
  }
}
  1. 原有页面的调用逻辑无需修改,你现在每个子页面单独把AppBottomNavigation放在自身Scaffold的bottomNavigationBar属性的用法可以保留,不需要把所有页面包在同一个Scaffold里,不会触发你之前遇到的首页加载异常问题。
  • 滚动位置自动保留说明:因为你用Navigator.push把新页面加到导航栈时,原有页面没有被销毁,所以SingleChildScrollView的滚动状态会天然保留,pop回到上一级页面时滚动位置不会丢失。
  • 系统返回键兼容:不管是用户点击AppBar的返回按钮还是安卓实体返回键触发pop,都会自动触发then回调更新底部导航索引,不需要额外加WillPopScope处理。

内容的提问来源于stack exchange,提问作者Mo7ammed 7amad

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 02:54:03