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

Flutter中实现GridView内Button单选功能的方案咨询

实现GridView中Button的单选功能

要实现每次仅选中一个Button的功能,需要同时修改数据模型(CardData)、卡片组件(my_card.dart)和网格容器(my_card_grid.dart)——核心思路是把选中状态从单个卡片的局部状态,提升到全局数据层统一管理,让所有卡片共享同一个选中状态源。


1. 更新数据模型(CardData)

在card_data.dart的MyCardData类中添加选中状态的管理逻辑,用来记录当前选中的卡片唯一标识:

class MyCardData extends ChangeNotifier {
  // 原有代码(卡片列表、增删方法等)保持不变
  List<MyCard> cards = [];
  int get cardCount => cards.length;

  // 新增:存储当前选中卡片的ID,未选中时为null
  int? selectedCardId;

  // 新增:更新选中卡片的方法
  void selectCard(int cardId) {
    selectedCardId = cardId;
    notifyListeners(); // 通知所有监听组件更新UI
  }
}

注:假设你的MyCard模型类有唯一标识id,如果没有可以改用卡片索引或对象本身来区分。


2. 修改卡片组件(my_card.dart)

移除卡片内部的selectedCard局部状态,改为接收外部传入的选中状态和点击回调,让组件变成纯展示型:

// 改为StatelessWidget(如果不需要其他内部状态)
class MyCard extends StatelessWidget {
  final String cardTitle;
  final Color pickerColor;
  final VoidCallback deleteCallback;
  final bool isSelected;
  final VoidCallback onTapCallback;

  const MyCard({
    super.key,
    required this.cardTitle,
    required this.pickerColor,
    required this.deleteCallback,
    required this.isSelected,
    required this.onTapCallback,
  });

  @override
  Widget build(BuildContext context) {
    return TextButton(
      onLongPress: () => showDialog<String>(
        // 原有弹窗逻辑完全保留
        context: context,
        builder: (BuildContext context) => AlertDialog(
          actionsAlignment: MainAxisAlignment.spaceEvenly,
          title: Center(
            child: Text(
              cardTitle,
              style: TextStyle(
                color: pickerColor,
                fontWeight: FontWeight.bold,
                shadows: [
                  Shadow(
                    color: Colors.black.withOpacity(0.3),
                    offset: const Offset(0, 0),
                    blurRadius: 15,
                  ),
                ],
              ),
            ),
          ),
          content: const Text(
            'Möchtest du die Karte wirklich löschen?',
            textAlign: TextAlign.center,
            style: TextStyle(fontSize: 18),
          ),
          actions: <Widget>[
            TextButton(
              style: ButtonStyle(
                shape: MaterialStateProperty.all<RoundedRectangleBorder>(
                  RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))
                ),
                elevation: MaterialStateProperty.all(10),
                backgroundColor: MaterialStateProperty.all(Colors.white),
              ),
              onPressed: () {
                deleteCallback();
                Navigator.pop(context);
              },
              child: const Text(
                'Karte löschen',
                style: TextStyle(
                  color: Colors.black,
                  fontWeight: FontWeight.bold,
                  fontSize: 16,
                ),
              ),
            ),
            TextButton(
              style: ButtonStyle(
                shape: MaterialStateProperty.all<RoundedRectangleBorder>(
                  RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))
                ),
                elevation: MaterialStateProperty.all(10),
                backgroundColor: MaterialStateProperty.all(Colors.white),
              ),
              onPressed: () => Navigator.pop(context),
              child: const Text(
                'Abbrechen',
                style: TextStyle(color: Colors.black, fontSize: 16),
              ),
            ),
          ],
        ),
      ),
      style: ButtonStyle(
        side: MaterialStateProperty.all(BorderSide(
          width: 5,
          color: isSelected ? Colors.black : Colors.white,
        )),
        shape: MaterialStateProperty.all(
          RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))
        ),
        backgroundColor: MaterialStateProperty.all(pickerColor),
        elevation: MaterialStateProperty.all(10),
      ),
      onPressed: onTapCallback,
      child: FittedBox(
        fit: BoxFit.fitHeight,
        child: Text(
          cardTitle,
          style: TextStyle(
            fontSize: 17,
            color: useWhiteForeground(pickerColor)
                ? const Color(0xffffffff)
                : const Color(0xff000000),
          ),
        ),
      ),
    );
  }
}

3. 修改网格容器(my_card_grid.dart)

在构建MyCard时,传入当前卡片的选中状态判断结果,以及点击时触发全局选中状态更新的回调:

class CardsGrid extends StatelessWidget {
  const CardsGrid({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Consumer<MyCardData>(
      builder: (context, cardData, child) {
        return Padding(
          padding: const EdgeInsets.all(10),
          child: GridView.builder(
            clipBehavior: Clip.none,
            itemBuilder: (context, index) {
              final card = cardData.cards[index];
              return MyCard(
                cardTitle: card.name,
                pickerColor: card.cardColor,
                deleteCallback: () {
                  cardData.deleteCallback(card);
                },
                // 判断当前卡片是否为选中状态
                isSelected: cardData.selectedCardId == card.id,
                // 点击时更新全局选中的卡片ID
                onTapCallback: () {
                  cardData.selectCard(card.id);
                },
              );
            },
            itemCount: cardData.cardCount,
            gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
              maxCrossAxisExtent: 150,
              childAspectRatio: 2.5 / 1,
              crossAxisSpacing: 0,
              mainAxisSpacing: 0,
            ),
          ),
        );
      },
    );
  }
}

原理说明

原来的实现中每个MyCard独立维护自己的选中状态,彼此没有关联,所以点击新卡片时旧卡片的选中状态不会改变。现在将选中状态统一放在全局的MyCardData中管理,所有卡片共享这一个状态源:点击任意卡片时,会更新全局的选中ID,所有卡片都会根据这个ID重新判断自己的选中状态,从而实现单选效果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 01:55:20