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
相关产品推荐
相关产品推荐

