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

Flutter使用Bloc实现Toggle Button无法切换的问题排查

问题:Flutter ToggleButton迁移到Bloc后无法正常切换

尝试将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)),
                      ),
                  ],
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

关键修复说明

  1. 事件传递索引:给SelectSize事件新增selectedIndex参数,将点击的按钮索引传递到Bloc中
  2. 遵循不可变原则:创建sizeSelection的副本进行修改,避免直接操作原State对象
  3. 动态初始化状态:通过工厂方法fromProduct根据产品尺寸数量生成对应长度的初始选中状态列表
  4. 简化状态逻辑:移除不必要的状态枚举值,只保留initial和selected两种状态
  5. UI组件优化:将ProductSize改为无状态组件,减少不必要的本地状态管理

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 14:20:23