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

如何阻止Bloc刷新列表中的所有项?

Bloc 可展开列表全量刷新问题解决

我正在学习Bloc,但操作上出了问题——每次触发事件时,列表里的所有项都会刷新!
这是一个可展开视图列表,点击列表项时它会改变尺寸并展开。

原代码情况

Event类

class HelpEvents {}

class ExpandView extends HelpEvents {}

class CollapseView extends HelpEvents {}

State类

class HelpStates {}

class ToggleViewState extends HelpStates {}

Bloc类

class HelpScreenBloc extends Bloc<HelpEvents, HelpStates> {

  HelpScreenBloc() : super(HelpStates()){ //InitialState

    /// When user press Expand
    on<ExpandView>((event, emit) async {
      emit(ToggleViewState()); //first state
    });

    /// When user press Collapse
    on<CollapseView>((event, emit) {
      emit(ToggleViewState()); //collapsed state
    });
  }
}

列表项代码

bool isExpanded = false;

@override
Widget build(BuildContext context) {
  return Padding(
    padding: const EdgeInsets.all(20),

    child: Material(
        elevation: appElevation,

        child: BlocBuilder<HelpScreenBloc, HelpStates>(
          builder: (context, state) {
            print("This is supposed to run Once");

            return InkWell(

              onTap: () {

                // <-- Trigger Event ---------------- here

                if(!isExpanded) {
                  context.read<HelpScreenBloc>().add(ExpandView());

                } else {
                  context.read<HelpScreenBloc>().add(CollapseView());
                }

                isExpanded = !isExpanded;
              },

              child: Expanded(
                child: AnimatedContainer(
                    height: isExpanded ? 150 : 70,
                    duration: const Duration(milliseconds: 180),

                    child: const Text("text"),
                ),
              ),
            );
          }
        )
    ),
  );
}

我还发现用State变量会改变列表中的所有项!这就是为什么我被迫在Widget中使用全局bool变量isExpanded,但我觉得这不太规范,应该在State类中操作变量并通过state.variable引用。


问题根源

  1. Bloc状态无项区分:当前ToggleViewState没有携带任何列表项的唯一标识,所有列表项的BlocBuilder都会在状态更新时强制重建,导致全量刷新。
  2. 状态管理不独立:全局isExpanded是所有列表项共用的变量,修改它会同步影响所有项;就算用StatefulWidget的本地状态,若列表项未做隔离复用,也会出现批量修改的问题。

修正方案

要让每个列表项的展开状态独立管理,需要让Bloc的状态记录每个项的标识对应的展开状态,具体步骤如下:

1. 修改Event类,添加项唯一标识

让展开/折叠事件携带列表项ID,让Bloc能定位到具体操作的项:

abstract class HelpEvents {}

class ExpandView extends HelpEvents {
  final String itemId; // 列表项唯一标识
  ExpandView(this.itemId);
}

class CollapseView extends HelpEvents {
  final String itemId;
  CollapseView(this.itemId);
}

2. 修改State类,存储多状态映射

用Map<String, bool>记录每个itemId对应的展开状态,同时提供复制方法用于状态更新:

class HelpState {
  final Map<String, bool> expandedStates; // key: itemId, value: 是否展开

  HelpState({required this.expandedStates});

  // 复制现有状态并更新指定项,避免直接修改原状态
  HelpState copyWith({Map<String, bool>? expandedStates}) {
    return HelpState(
      expandedStates: expandedStates ?? this.expandedStates,
    );
  }
}

3. 修改Bloc类,处理带标识的事件

初始化时创建空状态映射,根据事件中的itemId更新对应项的状态:

class HelpScreenBloc extends Bloc<HelpEvents, HelpState> {
  HelpScreenBloc() : super(HelpState(expandedStates: {})) {
    on<ExpandView>((event, emit) {
      final newStates = Map<String, bool>.from(state.expandedStates);
      newStates[event.itemId] = true;
      emit(state.copyWith(expandedStates: newStates));
    });

    on<CollapseView>((event, emit) {
      final newStates = Map<String, bool>.from(state.expandedStates);
      newStates[event.itemId] = false;
      emit(state.copyWith(expandedStates: newStates));
    });
  }
}

4. 修改列表项Widget,从Bloc状态获取状态

移除全局isExpanded,通过当前项ID从Bloc状态中读取展开状态,点击时发送带ID的事件:

class HelpListItem extends StatelessWidget {
  final String itemId;
  final double appElevation;

  const HelpListItem({super.key, required this.itemId, required this.appElevation});

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(20),
      child: Material(
        elevation: appElevation,
        child: BlocBuilder<HelpScreenBloc, HelpState>(
          // 可选优化:只有当前项状态变化时才重建
          buildWhen: (previous, current) {
            return previous.expandedStates[itemId] != current.expandedStates[itemId];
          },
          builder: (context, state) {
            // 默认未展开
            final isExpanded = state.expandedStates[itemId] ?? false;
            
            return InkWell(
              onTap: () {
                if (!isExpanded) {
                  context.read<HelpScreenBloc>().add(ExpandView(itemId));
                } else {
                  context.read<HelpScreenBloc>().add(CollapseView(itemId));
                }
              },
              child: AnimatedContainer(
                height: isExpanded ? 150 : 70,
                duration: const Duration(milliseconds: 180),
                child: const Text("text"),
              ),
            );
          },
        ),
      ),
    );
  }
}

这样修改后,每个列表项的展开状态会独立存储在Bloc的State中,点击某一项只会更新对应项的状态,其他列表项不会触发重建,既解决了全量刷新问题,也符合Bloc的状态管理规范。

内容的提问来源于stack exchange,提问作者Ziad H.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 18:56:19