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

Flutter自定义OutlinedButton单选按钮无法互斥的解决方法

Flutter自定义OutlinedButton实现单选互斥

问题背景

现有一段自定义按钮样式的代码片段,原本用于实现单选按钮效果:

Widget DryerRadio(String text, int index) {
    return OutlinedButton(
      onPressed: () {
        setState(
          () {
            dryer = index;
          },
        );
      },
      style: OutlinedButton.styleFrom(
        side: BorderSide(
            color: (dryer == index) ? Colors.lightBlue : Colors.white),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(10),
        ),
      ),
      child: Text(
        text,
        style: TextStyle(
            color: (dryer == index) ? Colors.lightBlue : Colors.white),
      ),
    );
  }

尝试将其封装为独立的可复用Stateful组件后,代码如下:

class DetergentRadio extends StatefulWidget {
  DetergentRadio({super.key, required this.text, required this.index});
  String text;
  int index;

  @override
  State<DetergentRadio> createState() => _DetergentRadioState();
}

class _DetergentRadioState extends State<DetergentRadio> {
  int detergent = 0;
  @override
  Widget build(BuildContext context) {
    return OutlinedButton(
      onPressed: () {
        setState(() {
          detergent = widget.index;
        });
      },
      style: OutlinedButton.styleFrom(
        side: BorderSide(
            color:
                (detergent == widget.index) ? Colors.lightBlue : Colors.white),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
      ),
      child: Text(
        widget.text,
        style: TextStyle(
            color:
                (detergent == widget.index) ? Colors.lightBlue : Colors.white),
      ),
    );
  }
}

封装后运行出现异常:点击多个按钮时可以同时选中多个选项,无法实现单选要求的同组选项互斥、同一时间仅选中一个的效果。

问题根因

互斥失效的核心原因是状态存放位置错误:选中状态被封装在每个单独的按钮组件内部。
每实例化一个DetergentRadio组件,都会在其独立的State中创建专属的detergent变量。点击某个按钮时,只会修改当前按钮内部的状态值,同组其他按钮的状态完全不受影响,自然无法实现互斥。

修复实现

单选组件的核心规则是:同组单选的选中状态必须由所有选项的共同父组件统一维护,子组件只负责展示样式和触发点击回调,不持有选中状态。

改造后的可复用单选按钮组件

class CustomRadioButton extends StatelessWidget {
  const CustomRadioButton({
    super.key,
    required this.text,
    required this.index,
    required this.selectedIndex,
    required this.onSelected,
  });

  // 按钮显示文本
  final String text;
  // 当前按钮对应的选项索引
  final int index;
  // 父组件传入的当前整组选中的索引
  final int selectedIndex;
  // 点击回调:通知父组件更新选中值
  final ValueChanged<int> onSelected;

  @override
  Widget build(BuildContext context) {
    return OutlinedButton(
      onPressed: () => onSelected(index),
      style: OutlinedButton.styleFrom(
        side: BorderSide(
            color: selectedIndex == index ? Colors.lightBlue : Colors.white),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(10),
        ),
      ),
      child: Text(
        text,
        style: TextStyle(
            color: selectedIndex == index ? Colors.lightBlue : Colors.white),
      ),
    );
  }
}

父组件调用示例

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

  @override
  State<RadioGroupPage> createState() => _RadioGroupPageState();
}

class _RadioGroupPageState extends State<RadioGroupPage> {
  // 父组件统一维护整组单选的选中值,默认选中索引0的选项
  int currentSelected = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            CustomRadioButton(
              text: "标准洗",
              index: 0,
              selectedIndex: currentSelected,
              onSelected: (val) => setState(() => currentSelected = val),
            ),
            const SizedBox(width: 12),
            CustomRadioButton(
              text: "快洗",
              index: 1,
              selectedIndex: currentSelected,
              onSelected: (val) => setState(() => currentSelected = val),
            ),
            const SizedBox(width: 12),
            CustomRadioButton(
              text: "柔洗",
              index: 2,
              selectedIndex: currentSelected,
              onSelected: (val) => setState(() => currentSelected = val),
            ),
          ],
        ),
      ),
    );
  }
}

改造要点

  • 单选按钮本身不需要持有内部状态,使用StatelessWidget实现即可,避免组件间状态隔离
  • 选中状态统一提升到同组按钮的共同父级维护,保证整组选项共享同一个数据源
  • 子组件点击时通过回调把选中值传给父组件,父组件触发重建后将最新选中值同步给所有子组件,自动刷新选中样式
  • 同组单选按钮传入同一个状态值和更新回调,天然实现互斥逻辑,不同组单选按钮单独维护各自的选中状态即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 04:06:51