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

Flutter中如何实现多下拉菜单选项唯一选中,后选覆盖前选?

实现下拉菜单选项互斥的解决方案

要实现「同一选项只能被最后选中的下拉菜单保留,之前选中该选项的下拉菜单自动清空」的效果,核心是将所有卡片的选中状态统一移到父组件管理,通过全局状态控制互斥逻辑。以下是具体实现步骤:


1. 修改 CardWidget 组件

将 CardWidget 改为无状态组件,把选中状态、可用选项列表和选中回调交给父组件控制:

class CardWidget extends StatelessWidget {
  final String title;
  final Color color;
  final IconData icon;
  final int cardId; // 每个卡片的唯一标识
  final String? selectedValue; // 父组件传递的当前选中值
  final List<String> availableItems; // 可用选项(排除已被其他卡片占用的)
  final Function(int, String?) onSelectionChanged; // 选中变化的回调

  const CardWidget({
    super.key,
    required this.title,
    required this.color,
    required this.icon,
    required this.cardId,
    required this.selectedValue,
    required this.availableItems,
    required this.onSelectionChanged,
  });

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 200,
      width: 200,
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Card(
          elevation: 10,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(20.0),
          ),
          color: color,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              Icon(
                icon,
                color: Colors.white,
                size: 32,
              ),
              Center(
                child: Text(
                  title,
                  style: GoogleFonts.poppins(
                      color: Colors.white,
                      fontSize: 14,
                      fontWeight: FontWeight.bold),
                ),
              ),
              DropdownButtonHideUnderline(
                child: DropdownButton2(
                  isExpanded: true,
                  hint: const Row(
                    children: [
                      Expanded(
                        child: Center(
                          child: Text(
                            'Select a Key',
                            style: TextStyle(color: Colors.black87),
                            overflow: TextOverflow.ellipsis,
                          ),
                        ),
                      ),
                    ],
                  ),
                  items: availableItems
                      .map((item) => DropdownMenuItem<String>(
                            value: item,
                            child: Center(
                              child: Text(
                                item,
                                style: const TextStyle(color: Colors.black87),
                                overflow: TextOverflow.ellipsis,
                              ),
                            ),
                          ))
                      .toList(),
                  value: selectedValue,
                  onChanged: (value) => onSelectionChanged(cardId, value),
                  icon: const Icon(Icons.arrow_forward_ios_outlined),
                  iconSize: 20,
                  iconEnabledColor: const Color.fromARGB(255, 31, 10, 88),
                  iconDisabledColor: Colors.grey,
                  buttonHeight: 40,
                  buttonWidth: 135,
                  buttonPadding: const EdgeInsets.only(left: 15, right: 15),
                  buttonDecoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(14),
                    border: Border.all(color: Colors.black26),
                    color: Colors.white,
                  ),
                  buttonElevation: 2,
                  itemHeight: 40,
                  itemPadding: const EdgeInsets.only(left: 14, right: 14),
                  dropdownMaxHeight: 200,
                  dropdownWidth: 200,
                  dropdownDecoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(10),
                    color: Colors.white,
                  ),
                  dropdownElevation: 8,
                  scrollbarRadius: const Radius.circular(10),
                  scrollbarThickness: 6,
                  scrollbarAlwaysShow: true,
                  offset: const Offset(-20, 0),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

2. 父组件(首页)实现互斥逻辑

在父组件中维护全局状态,处理选中变化时的互斥规则:

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  // 所有可选的选项列表
  final List<String> _allItems = ['Key 1', 'Key 2', 'Key 3', 'Key 4'];
  // 记录每个卡片的选中值:key=卡片id,value=选中的选项
  final Map<int, String?> _cardSelections = {};
  // 记录每个选项的归属卡片:key=选项,value=卡片id
  final Map<String, int?> _optionOwners = {};

  // 处理下拉菜单选中变化的核心逻辑
  void _handleSelectionChanged(int cardId, String? newValue) {
    setState(() {
      // 1. 清空当前卡片之前选中选项的归属记录
      final oldValue = _cardSelections[cardId];
      if (oldValue != null) {
        _optionOwners.remove(oldValue);
      }

      // 2. 如果新选项已被其他卡片占用,清空该卡片的选中值
      if (newValue != null) {
        final occupiedCardId = _optionOwners[newValue];
        if (occupiedCardId != null && occupiedCardId != cardId) {
          _cardSelections[occupiedCardId] = null;
          _optionOwners.remove(newValue);
        }
        // 记录新选项的归属卡片
        _optionOwners[newValue] = cardId;
      }

      // 3. 更新当前卡片的选中值
      _cardSelections[cardId] = newValue;
    });
  }

  // 为每个卡片过滤可用选项:排除被其他卡片占用的选项(自己已选中的除外)
  List<String> _getAvailableItems(int cardId) {
    final currentSelection = _cardSelections[cardId];
    return _allItems.where((item) {
      final owner = _optionOwners[item];
      return owner == null || owner == cardId;
    }).toList();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                // 给每个卡片分配唯一ID(0-3)
                CardWidget(
                  title: 'Mute',
                  color: const Color.fromARGB(255, 211, 38, 25),
                  icon: Icons.volume_off_outlined,
                  cardId: 0,
                  selectedValue: _cardSelections[0],
                  availableItems: _getAvailableItems(0),
                  onSelectionChanged: _handleSelectionChanged,
                ),
                CardWidget(
                  title: 'Mute',
                  color: const Color.fromARGB(255, 211, 38, 25),
                  icon: Icons.volume_off_outlined,
                  cardId: 1,
                  selectedValue: _cardSelections[1],
                  availableItems: _getAvailableItems(1),
                  onSelectionChanged: _handleSelectionChanged,
                ),
                CardWidget(
                  title: 'Mute',
                  color: const Color.fromARGB(255, 211, 38, 25),
                  icon: Icons.volume_off_outlined,
                  cardId: 2,
                  selectedValue: _cardSelections[2],
                  availableItems: _getAvailableItems(2),
                  onSelectionChanged: _handleSelectionChanged,
                ),
                CardWidget(
                  title: 'Mute',
                  color: const Color.fromARGB(255, 211, 38, 25),
                  icon: Icons.volume_off_outlined,
                  cardId: 3,
                  selectedValue: _cardSelections[3],
                  availableItems: _getAvailableItems(3),
                  onSelectionChanged: _handleSelectionChanged,
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

核心逻辑说明

  1. 状态上移:将每个卡片的选中状态从组件内部移到父组件,实现全局状态统一管理。
  2. 双向映射:通过两个Map分别记录「卡片→选中选项」和「选项→所属卡片」,快速定位和更新互斥关系。
  3. 互斥处理:当新选项被选中时,自动清空之前占用该选项的卡片选中值,并更新归属记录。
  4. 选项过滤:每个卡片仅显示未被其他卡片占用的选项,避免无效选择,提升交互体验。

内容的提问来源于stack exchange,提问作者ckot

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 22:15:49