Flutter使用Bloc实现Toggle Button无法切换的问题排查
尝试将Flutter的Toggle Button从setState迁移到Bloc实现,但无法让切换按钮正常切换,以下是问题分析及修复方案:
核心问题梳理
- 事件未传递点击索引:
SelectSize事件未携带点击的按钮索引,Bloc中依赖的类变量newIndex始终为0,导致只会操作第一个按钮 - 直接修改不可变State:Bloc的State是不可变对象,直接修改
state.sizeSelection会导致状态无法正确更新 - 状态初始化错误:
ToggleSizeState中sizeSelection硬编码为固定长度列表,未根据产品尺寸数量动态生成 - Bloc逻辑错误:循环逻辑未正确处理选中状态,且未基于当前状态创建新的列表副本
修复后的完整代码
1. Bloc Event 代码
abstract class ToggleSizeEvent extends Equatable { const ToggleSizeEvent(); @override List<Object?> get props => []; } class SelectSize extends ToggleSizeEvent { const SelectSize({ required this.idSelected, required this.selectedIndex, // 新增:传递点击的按钮索引 }); final int idSelected; final int selectedIndex; @override List<Object?> get props => [idSelected, selectedIndex]; }
2. State 代码
enum ToggleSizeStatus { initial, selected } extension ToggleSizeStatusX on ToggleSizeStatus { bool get isInitial => this == ToggleSizeStatus.initial; bool get isSelected => this == ToggleSizeStatus.selected; } class ToggleSizeState extends Equatable { final List<bool> sizeSelection; final ToggleSizeStatus status; final int idSelected; final String size; final int index; const ToggleSizeState({ required this.sizeSelection, this.status = ToggleSizeStatus.initial, this.idSelected = 0, this.size = '', this.index = 0, }); // 根据产品尺寸列表动态生成初始状态 factory ToggleSizeState.fromProduct(ProductModel product) { return ToggleSizeState( sizeSelection: List.generate(product.size!.length, (_) => false), ); } @override List<Object> get props => [status, sizeSelection, idSelected, size, index]; ToggleSizeState copyWith({ List<bool>? sizeSelection, ToggleSizeStatus? status, int? idSelected, String? size, int? index, }) { return ToggleSizeState( sizeSelection: sizeSelection ?? this.sizeSelection, status: status ?? this.status, idSelected: idSelected ?? this.idSelected, size: size ?? this.size, index: index ?? this.index, ); } }
3. Bloc 代码
part 'toggle_size_event.dart'; part 'toggle_size_state.dart'; class ToggleSizeBloc extends Bloc<ToggleSizeEvent, ToggleSizeState> { final ProductModel product; ToggleSizeBloc({required this.product}) : super(ToggleSizeState.fromProduct(product)) { on<SelectSize>(_mapSelectSizeEventToState); } void _mapSelectSizeEventToState( SelectSize event, Emitter<ToggleSizeState> emit) { // 创建列表副本,避免直接修改原State(不可变原则) final newSizeSelection = List<bool>.from(state.sizeSelection); // 重置所有选项为未选中 for (int i = 0; i < newSizeSelection.length; i++) { newSizeSelection[i] = false; } // 切换当前点击选项的选中状态 newSizeSelection[event.selectedIndex] = !newSizeSelection[event.selectedIndex]; emit(state.copyWith( status: ToggleSizeStatus.selected, idSelected: event.idSelected, sizeSelection: newSizeSelection, index: event.selectedIndex, )); } }
4. UI 代码
class ProductSize extends StatelessWidget { // 改为无状态组件,无需本地state final ProductModel product; const ProductSize({super.key, required this.product}); @override Widget build(BuildContext context) { return Container( color: Colors.white, child: Column( children: [ const Text('Please select size'), BlocProvider( create: (context) => ToggleSizeBloc(product: product), child: BlocBuilder<ToggleSizeBloc, ToggleSizeState>( builder: (context, state) { debugPrint('ToggleSize Page ${state.sizeSelection}'); return ToggleButtons( selectedColor: Colors.white, color: Colors.black, fillColor: Colors.purple.shade100, splashColor: Colors.purple.shade200, highlightColor: Colors.purple.shade200, textStyle: const TextStyle(fontWeight: FontWeight.bold), renderBorder: true, borderColor: Colors.purple.shade200, borderWidth: 1.5, borderRadius: BorderRadius.circular(10), selectedBorderColor: Colors.purple.shade200, isSelected: state.sizeSelection, onPressed: (int newIndex) { context.read<ToggleSizeBloc>().add(SelectSize( idSelected: product.id!, selectedIndex: newIndex, // 传递点击的索引到事件 )); }, children: [ for (var item in product.size!) Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Text(item, style: const TextStyle(fontSize: 18)), ), ], ); }, ), ), ], ), ); } }
关键修复说明
- 事件传递索引:给
SelectSize事件新增selectedIndex参数,将点击的按钮索引传递到Bloc中 - 遵循不可变原则:创建
sizeSelection的副本进行修改,避免直接操作原State对象 - 动态初始化状态:通过工厂方法
fromProduct根据产品尺寸数量生成对应长度的初始选中状态列表 - 简化状态逻辑:移除不必要的状态枚举值,只保留
initial和selected两种状态 - UI组件优化:将
ProductSize改为无状态组件,减少不必要的本地状态管理
内容的提问来源于stack exchange,提问作者pythonGo
相关产品推荐
相关产品推荐

