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

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:全局状态共享(简单场景快速实现)

把选中的菜单索引抽离为全局变量,让所有抽屉实例共享这个状态:

  1. 创建全局状态管理类
class DrawerStateManager {
  static int selectedIndex = 0; // 默认选中第一个菜单

  static void updateSelectedIndex(int index) {
    selectedIndex = index;
  }
}
  1. 修改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,
    );
  },
),
  1. 更新drawerItems的点击事件
    去掉原有的循环和setState,直接更新全局索引并跳转:
onTap: () {
  DrawerStateManager.updateSelectedIndex(index);
  Navigator.of(context).pushReplacement(
    MaterialPageRoute(builder: ((context) => path)),
  );
},

方案2:SharedPreferences持久化(重启APP仍保留状态)

如果需要重启应用后依然记住选中状态,用SharedPreferences存储选中索引:

  1. 在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;
    });
  }
  1. 修改列表构建的选中判断
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,
    );
  },
),
  1. 更新点击事件逻辑
onTap: () async {
  await _saveSelectedIndex(index);
  Navigator.of(context).pushReplacement(
    MaterialPageRoute(builder: ((context) => path)),
  );
},

方案3:父级统一管理抽屉(推荐单页面架构)

如果应用采用单页面+内容切换的架构,把抽屉放在父级Scaffold中,所有页面作为body切换,抽屉仅创建一次,状态不会丢失:

  1. 创建父级主页面
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],
    );
  }
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 22:09:22