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

基于Flutter Bloc的ListView分页滚动位置丢失问题求助

解决Bloc分页时ListView滚动位置重置问题

问题根源

滚动位置跳回顶部的核心原因:

  • 分页加载时,Bloc先emit了DaumListLoadingState,导致BlocBuilder重建并返回SingleChildScrollView,原有ListView被销毁
  • 加载完成后emitDaumListLoadedState,ListView重新创建,滚动状态完全丢失
  • 使用serviceLocator获取Bloc实例可能存在上下文不一致的问题

修复步骤

1. 修改状态类,添加加载状态标记

在DaumListLoadedState中新增isLoading字段,避免切换到单独的加载状态:

class DaumListLoadedState extends DaumListState {
  final DaumList daumList;
  final bool isLoading;

  DaumListLoadedState(this.daumList, {this.isLoading = false});
}

2. 调整Bloc分页逻辑,移除单独的加载状态emit

修改FetchDaumListNextPageEvent的处理逻辑,直接在已加载状态上标记加载状态,不替换整个状态类型:

on<FetchDaumListNextPageEvent>(((event, emit) async {
  final currentState = state;

  if (currentState is DaumListLoadedState) {
    // 标记当前列表处于加载状态,保留原有数据
    emit(DaumListLoadedState(currentState.daumList, isLoading: true));

    final nextToken = currentState.daumList.nextToken;
    final queryExecutionId = currentState.daumList.queryExecutionId;

    try {
      final DaumList data = await daumRepository?.fetchDaumList(
              nextToken: nextToken, queryExecutionId: queryExecutionId) ??
          DaumList(nextToken: "", queryExecutionId: "", value: []);

      final DaumList concatenatedData = DaumList(
          nextToken: data.nextToken,
          queryExecutionId: data.queryExecutionId,
          value: currentState.daumList.value + data.value);

      // 加载完成,更新数据并取消加载标记
      emit(DaumListLoadedState(concatenatedData, isLoading: false));
    } catch (e) {
      print(e);
      // 加载失败,恢复加载状态并保留原有数据
      emit(DaumListLoadedState(currentState.daumList, isLoading: false));
      emit(DaumListErrorState(e.toString()));
    }
  } else {
    emit(const DaumListErrorState(
        "pagination failed: previous data does not exist"));
  }
}));

3. 优化UI层代码

  • 使用context.read获取Bloc实例,保证上下文一致
  • 在ListView中添加加载指示器,无需替换整个视图:
@override
Widget build(BuildContext context) {
  return BlocBuilder<DaumListBloc, DaumListState>(builder: (context, state) {
    if (state is DaumListLoadedState) {
      List<DaumListValue> daumLists = state.daumList.value;

      return NotificationListener<ScrollNotification>(
        onNotification: (scrollInfo) {
          if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && !state.isLoading) {
            // 避免重复触发加载
            context.read<DaumListBloc>().add(FetchDaumListNextPageEvent());
          }
          return true;
        },
        child: ListView.builder(
          itemCount: daumLists.length + (state.isLoading ? 1 : 0),
          itemBuilder: (BuildContext context, int index) {
            if (index == daumLists.length) {
              // 底部显示加载指示器
              return const Padding(
                padding: EdgeInsets.symmetric(vertical: 16),
                child: Center(child: CircularProgressIndicator()),
              );
            }
            return DaumListWidget(
                title: daumLists[index].title,
                departureDate: daumLists[index].date,
                thumbnail: daumLists[index].thumbnail,
                subTitle: daumLists[index].title);
          },
        ),
      );
    }
    // 默认加载视图
    return const Center(child: CircularProgressIndicator());
  });
}

额外优化建议

  • 给ListView设置一个稳定的key(比如GlobalKey()),进一步确保滚动状态的保留
  • 在滚动监听中增加!state.isLoading判断,避免多次触发分页请求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 23:54:54