Flutter中如何保留抽屉导航的选中状态?
Flutter抽屉菜单选中状态跳转后丢失的解决方法
问题背景
我正在开发一个包含7个页面的Flutter应用,单独封装了DrawerClass类,每个页面的Scaffold都通过drawer: DrawerClass()调用该抽屉。点击抽屉菜单项时,该项会变为琥珀色并跳转对应页面,但进入新页面后再次打开抽屉,之前的选中状态就丢失了,琥珀色消失。需要实现记住最近选中的抽屉菜单项。
当前DrawerClass核心代码如下:
ListView.builder部分
ListView.builder( shrinkWrap: true, itemCount: drawerMenu.length, itemBuilder: (context, index) { return drawerItems( index, context, drawerMenu[index]['title'], drawerMenu[index]['screen'], drawerMenu[index]['isHighlighted'] ? Colors.amber : Colors.white, ); }, ),
drawerItems方法部分
drawerItems( int index, BuildContext context, String title, path, Color color) { return Padding( padding: EdgeInsets.only( top: 0.0, left: MediaQuery.of(context).size.width * 0.1), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ GestureDetector( onTap: () async { //final SharedPreferences prefs = await _prefs; for (int i = 0; i < drawerMenu.length; i++) { setState(() { if (index == i) { drawerMenu[index]['isHighlighted'] = true; } else { //the condition to change the highlighted item drawerMenu[i]['isHighlighted'] = false; } }); Navigator.of(context).pushReplacement( MaterialPageRoute(builder: ((context) => path)), ); } }, child: Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Text( title, style: TextStyle( color: color, fontFamily: "Raleway Reg", fontSize: 23, letterSpacing: 2), ), ), ), SizedBox( width: MediaQuery.of(context).size.width * 0.5, child: const Divider( thickness: 1, color: Colors.white, height: 3, ), ), ])); }
问题原因
每个页面都创建了全新的DrawerClass实例,抽屉的选中状态(drawerMenu中的isHighlighted)仅存储在当前页面的抽屉实例中。跳转页面后,新页面的抽屉是初始化状态,之前的选中状态自然丢失。
解决方案
方案1:全局状态共享(简单场景快速实现)
把选中的菜单索引抽离为全局变量,让所有抽屉实例共享这个状态:
- 创建全局状态管理类
class DrawerStateManager { static int selectedIndex = 0; // 默认选中第一个菜单 static void updateSelectedIndex(int index) { selectedIndex = index; } }
- 修改
DrawerClass的列表构建逻辑
不再依赖drawerMenu的isHighlighted,改用全局选中索引判断:
ListView.builder( shrinkWrap: true, itemCount: drawerMenu.length, itemBuilder: (context, index) { final isSelected = DrawerStateManager.selectedIndex == index; return drawerItems( index, context, drawerMenu[index]['title'], drawerMenu[index]['screen'], isSelected ? Colors.amber : Colors.white, ); }, ),
- 更新
drawerItems的点击事件
去掉原有的循环和setState,直接更新全局索引并跳转:
onTap: () { DrawerStateManager.updateSelectedIndex(index); Navigator.of(context).pushReplacement( MaterialPageRoute(builder: ((context) => path)), ); },
方案2:SharedPreferences持久化(重启APP仍保留状态)
如果需要重启应用后依然记住选中状态,用SharedPreferences存储选中索引:
- 在
DrawerClass中初始化SharedPreferences
class DrawerClass extends StatefulWidget { @override _DrawerClassState createState() => _DrawerClassState(); } class _DrawerClassState extends State<DrawerClass> { late SharedPreferences _prefs; int _selectedIndex = 0; @override void initState() { super.initState(); _loadSavedIndex(); } // 加载本地存储的选中索引 Future<void> _loadSavedIndex() async { _prefs = await SharedPreferences.getInstance(); setState(() { _selectedIndex = _prefs.getInt('selected_drawer_index') ?? 0; }); } // 保存选中索引到本地 Future<void> _saveSelectedIndex(int index) async { await _prefs.setInt('selected_drawer_index', index); setState(() { _selectedIndex = index; }); }
- 修改列表构建的选中判断
ListView.builder( shrinkWrap: true, itemCount: drawerMenu.length, itemBuilder: (context, index) { return drawerItems( index, context, drawerMenu[index]['title'], drawerMenu[index]['screen'], _selectedIndex == index ? Colors.amber : Colors.white, ); }, ),
- 更新点击事件逻辑
onTap: () async { await _saveSelectedIndex(index); Navigator.of(context).pushReplacement( MaterialPageRoute(builder: ((context) => path)), ); },
方案3:父级统一管理抽屉(推荐单页面架构)
如果应用采用单页面+内容切换的架构,把抽屉放在父级Scaffold中,所有页面作为body切换,抽屉仅创建一次,状态不会丢失:
- 创建父级主页面
class MainScreen extends StatefulWidget { @override _MainScreenState createState() => _MainScreenState(); } class _MainScreenState extends State<MainScreen> { int _selectedIndex = 0; // 定义所有页面列表 final List<Widget> _pages = [ HomePage(), ProfilePage(), SettingsPage(), // ... 其他4个页面 ]; @override Widget build(BuildContext context) { return Scaffold( drawer: DrawerClass( selectedIndex: _selectedIndex, onItemTap: (index) { setState(() { _selectedIndex = index; }); Navigator.pop(context); // 点击后关闭抽屉 }, ), body: _pages[_selectedIndex], ); } }
- 修改
DrawerClass为无状态组件,接收选中索引和回调
class DrawerClass extends StatelessWidget { final int selectedIndex; final Function(int) onItemTap; const DrawerClass({ required this.selectedIndex, required this.onItemTap, Key? key, }) : super(key: key); @override Widget build(BuildContext context) { return Drawer( child: ListView.builder( shrinkWrap: true, itemCount: drawerMenu.length, itemBuilder: (context, index) { return drawerItems( index, context, drawerMenu[index]['title'], onItemTap, selectedIndex == index ? Colors.amber : Colors.white, ); }, ), ); } Widget drawerItems( int index, BuildContext context, String title, Function(int) onItemTap, Color color, ) { return Padding( padding: EdgeInsets.only( top: 0.0, left: MediaQuery.of(context).size.width * 0.1, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ GestureDetector( onTap: () => onItemTap(index), child: Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Text( title, style: TextStyle( color: color, fontFamily: "Raleway Reg", fontSize: 23, letterSpacing: 2, ), ), ), ), SizedBox( width: MediaQuery.of(context).size.width * 0.5, child: const Divider( thickness: 1, color: Colors.white, height: 3, ), ), ], ), ); } }
内容的提问来源于stack exchange,提问作者Usman Hafeez
相关产品推荐
相关产品推荐

