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

Flutter选中按钮不显示勾选标记及选择数量限制问题求助

Flutter分类按钮组件问题:点击勾选失效+选择数量限制需求

我在Flutter项目中实现分类按钮组件时遇到两个问题:

  • 点击按钮时预期显示勾选标记,但后续加载的按钮点击后无此效果
  • 需要实现选择限制:最多选择3个主分类,每个主分类下最多选择2个子分类

点击按钮勾选失效演示

点击按钮时应显示勾选标记,但后续出现的按钮点击后无此效果


现有代码

1. 分类按钮组件

class CategoriesButton {
  Widget categoriesbutton(
      {onPress, color, textColor, String? title, bool selected = false}) {
    return ElevatedButton(
      style: ElevatedButton.styleFrom(
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(30),
        ),
        backgroundColor: color,
        padding: const EdgeInsets.all(4),
      ),
      onPressed: onPress,
      child: SizedBox(
        width: 90,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            Icon(
              selected ? Icons.check : Icons.add,
            ),
            title!.text.color(Colors.black).fontFamily(bold).make(),
          ],
        ),
      ),
    );
  }
}

2. 子分类组件

import '../consts/consts.dart';

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

  @override
  State<SubCategories> createState() => SubCategoriesState();
}

class SubCategoriesState extends State<SubCategories> {
  get isSelected => null;

  @override
  Widget build(BuildContext context) {
    List<String> list = ['one', 'two', 'three', 'four', 'five', 'six'];

    List<Widget> widget = List.empty(growable: true);
    List<bool> isSelected = List.empty(growable: true);

    for (int i = 0; i < list.length; i++) {
      isSelected.add(false);
      widget.add(
        CategoriesButton().categoriesbutton(
          color: const Color.fromRGBO(134, 126, 108, 1),
          title: list[i],
          onPress: () {
            setState(() {
              isSelected[i] = isSelected[i] ? false : true;
            });
          },
          textColor: Colors.black,
          selected: isSelected[i],
        ),
      );
    }
    if (list.length > 3) {
      return Center(
        child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              widget[0],
              widget[1],
              widget[2],
            ],
          ),
          Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [widget[3], widget[4], widget[5]])
        ]),
      );
    } else {
      return Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: widget,
        ),
      );
    }
  }
}

3. 分类页面

import 'package:kalakumbh/widgets/subcategories.dart';
import 'package:kalakumbh/widgets/textfield.dart';
import '../../widgets/categoriesbutton.dart';

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

  @override
  State<CategoriesScreen> createState() => _CategoriesScreenState();
}

class _CategoriesScreenState extends State<CategoriesScreen> {
  List<bool> isExpanded = [
    false,
    false,
    false,
    false,
    false,
    false,
    false,
    false,
    false
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            textField(categories, categoriesHint, TextInputType.name, null),
            75.heightBox,
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                CategoriesButton().categoriesbutton(
                  color: Colors.orange,
                  title: categoryone,
                  textColor: Colors.black,
                  selected: isExpanded[0],
                  onPress: () {
                    setState(() {
                      isExpanded[0] = isExpanded[0] ? false : true;
                      isExpanded[1] = false;
                      isExpanded[2] = false;
                    });
                  },
                ),
                CategoriesButton().categoriesbutton(
                  color: Colors.orange,
                  title: categoryone,
                  textColor: Colors.black,
                  selected: isExpanded[1],
                  onPress: () {
                    setState(() {
                      isExpanded[1] = isExpanded[1] ? false : true;
                      isExpanded[0] = false;
                      isExpanded[2] = false;
                    });
                  },
                ),
                CategoriesButton().categoriesbutton(
                  color: Colors.orange,
                  title: categoryone,
                  textColor: Colors.black,
                  selected: isExpanded[2],
                  onPress: () {
                    setState(() {
                      isExpanded[2] = isExpanded[2] ? false : true;
                      isExpanded[1] = false;
                      isExpanded[0] = false;
                    });
                  },
                ),
              ],
            ),
            15.heightBox,
            if (isExpanded[0] || isExpanded[1] || isExpanded[2])
              const SubCategories(),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                CategoriesButton().categoriesbutton(
                  color: Colors.orange,
                  title: categoryone,
                  textColor: Colors.black,
                  selected: isExpanded[3],
                  onPress: () {
                    setState(() {
                      isExpanded[3] = isExpanded[3] ? false : true;
                      isExpanded[4] = false;
                      isExpanded[5] = false;
                    });
                  },
                ),
                CategoriesButton().categoriesbutton(
                  color: Colors.orange,
                  title: categoryone,
                  textColor: Colors.black,
                  selected: isExpanded[4],
                  onPress: () {
                    setState(() {
                      isExpanded[4] = isExpanded[4] ? false : true;
                      isExpanded[3] = false;
                      isExpanded[5] = false;
                    });
                  },
                ),
                CategoriesButton().categoriesbutton(
                  color: Colors.orange,
                  title: categoryone,
                  textColor: Colors.black,
                  selected: isExpanded[5],
                  onPress: () {
                    setState(() {
                      isExpanded[5] = isExpanded[5] ? false : true;
                      isExpanded[4] = false;
                      isExpanded[3] = false;
                    });
                  },
                ),
              ],
            ),
            15.heightBox,
            if (isExpanded[3] || isExpanded[4] || isExpanded[5])
              const SubCategories(),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                CategoriesButton().categoriesbutton(
                  color: Colors.orange,
                  title: categoryone,
                  textColor: Colors.black,
                  selected: isExpanded[6],
                  onPress: () {
                    setState(() {
                      isExpanded[6] = isExpanded[6] ? false : true;
                      isExpanded[7] = false;
                      isExpanded[8] = false;
                    });
                  },
                ),
                CategoriesButton().categoriesbutton(
                  color: Colors.orange,
                  title: categoryone,
                  textColor: Colors.black,
                  selected: isExpanded[7],
                  onPress: () {
                    setState(() {
                      isExpanded[7] = isExpanded[7] ? false : true;
                      isExpanded[6] = false;
                      isExpanded[8] = false;
                    });
                  },
                ),
                CategoriesButton().categoriesbutton(
                  color: Colors.orange,
                  title: categoryone,
                  textColor: Colors.black,
                  selected: isExpanded[8],
                  onPress: () {
                    setState(() {
                      isExpanded[8] = isExpanded[8] ? false : true;
                      isExpanded[7] = false;
                      isExpanded[6] = false;
                    });
                  },
                ),
              ],
            ),
            15.heightBox,
            if (isExpanded[6] || isExpanded[7] || isExpanded[8])
              const SubCategories(),
          ],
        ),
      )
          .box
          .rounded
          .padding(const EdgeInsets.all(16))
          .width(context.screenWidth)
          .make(),
    );
  }
}

问题修复与实现方案

1. 解决子分类按钮勾选失效问题

核心原因:SubCategoriesState中isSelected列表在build方法内声明,每次setState重建时都会被重新初始化,导致选择状态丢失。

修复代码:

class SubCategoriesState extends State<SubCategories> {
  List<bool> isSelected = [];
  List<String> list = ['one', 'two', 'three', 'four', 'five', 'six'];

  @override
  void initState() {
    super.initState();
    // 初始化选择状态列表,只执行一次
    isSelected = List.generate(list.length, (index) => false);
  }

  @override
  Widget build(BuildContext context) {
    List<Widget> widget = List.empty(growable: true);

    for (int i = 0; i < list.length; i++) {
      widget.add(
        CategoriesButton().categoriesbutton(
          color: const Color.fromRGBO(134, 126, 108, 1),
          title: list[i],
          onPress: () {
            setState(() {
              // 实现每个主分类下最多选2个子分类
              int selectedCount = isSelected.where((val) => val).length;
              if (isSelected[i]) {
                // 已选中则取消
                isSelected[i] = false;
              } else {
                // 未选中且已选数量不足2个时允许选中
                if (selectedCount < 2) {
                  isSelected[i] = true;
                }
              }
            });
          },
          textColor: Colors.black,
          selected: isSelected[i],
        ),
      );
    }
    // 原有布局代码保持不变
    if (list.length > 3) {
      return Center(
        child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [widget[0], widget[1], widget[2]],
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [widget[3], widget[4], widget[5]],
          )
        ]),
      );
    } else {
      return Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: widget,
        ),
      );
    }
  }
}

2. 实现主分类最多选3个的限制

核心修改:将原有的isExpanded改为记录选中状态,添加数量判断逻辑,并重构按钮创建代码避免重复。

修复代码:

class _CategoriesScreenState extends State<CategoriesScreen> {
  List<bool> isMainCategorySelected = List.generate(9, (index) => false);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            textField(categories, categoriesHint, TextInputType.name, null),
            75.heightBox,
            // 第一组主分类
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [0,1,2].map((index) => buildMainCategoryButton(index)).toList(),
            ),
            15.heightBox,
            // 显示第一组已选中的主分类对应的子分类
            ...[0,1,2].map((index) {
              return isMainCategorySelected[index] 
                  ? const SubCategories().paddingOnly(bottom: 15) 
                  : const SizedBox.shrink();
            }).toList(),
            // 第二组主分类
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [3,4,5].map((index) => buildMainCategoryButton(index)).toList(),
            ),
            15.heightBox,
            ...[3,4,5].map((index) {
              return isMainCategorySelected[index] 
                  ? const SubCategories().paddingOnly(bottom: 15) 
                  : const SizedBox.shrink();
            }).toList(),
            // 第三组主分类
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [6,7,8].map((index) => buildMainCategoryButton(index)).toList(),
            ),
            15.heightBox,
            ...[6,7,8].map((index) {
              return isMainCategorySelected[index] 
                  ? const SubCategories().paddingOnly(bottom: 15) 
                  : const SizedBox.shrink();
            }).toList(),
          ],
        ),
      )
          .box
          .rounded
          .padding(const EdgeInsets.all(16))
          .width(context.screenWidth)
          .make(),
    );
  }

  // 封装主分类按钮创建逻辑
  Widget buildMainCategoryButton(int index) {
    return CategoriesButton().categoriesbutton(
      color: isMainCategorySelected[index] ? Colors.orange : Colors.grey,
      title: categoryone,
      textColor: Colors.black,
      selected: isMainCategorySelected[index],
      onPress: () {
        setState(() {
          int selectedCount = isMainCategorySelected.where((val) => val).length;
          if (isMainCategorySelected[index]) {
            // 已选中则取消
            isMainCategorySelected[index] = false;
          } else {
            // 未选中且已选数量不足3个时允许选中
            if (selectedCount < 3) {
              isMainCategorySelected[index] = true;
            }
          }
        });
      },
    );
  }
}

3. 组件规范优化

将CategoriesButton改为StatelessWidget,符合Flutter组件设计规范:

class CategoriesButton extends StatelessWidget {
  final VoidCallback? onPress;
  final Color? color;
  final Color? textColor;
  final String? title;
  final bool selected;
  final String bold; // 需传入字体名称,或直接定义

  const CategoriesButton({
    super.key,
    this.onPress,
    this.color,
    this.textColor,
    this.title,
    this.selected = false,
    required this.bold,
  });

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      style: ElevatedButton.styleFrom(
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(30),
        ),
        backgroundColor: color,
        padding: const EdgeInsets.all(4),
      ),
      onPressed: onPress,
      child: SizedBox(
        width: 90,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            Icon(selected ? Icons.check : Icons.add),
            if (title != null)
              Text(
                title!,
                style: TextStyle(
                  color: Colors.black,
                  fontFamily: bold,
                ),
              ),
          ],
        ),
      ),
    );
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 07:13:08