使用infinite_scroll_pagination结合BLoC时,fetchNextPage在组件构建时立即触发而非滚动触发的问题
我之前也踩过这个坑!当时看着日志里一堆fetchNextPage的打印,头都大了,折腾了好半天才找到核心问题,咱们一步步来解决:
最可能的根源:UI层临时创建PagingState的方式错了
看你的代码,每次BlocBuilder rebuild时,你都在builder里新建一个PagingState:
state: PagingState( pages: [state.files], keys: [state.files.length], hasNextPage: state.hasNextPage, isLoading: state.isLoading, ),
这种做法会让PagedListView每次build都拿到全新的PagingState实例,它会误以为状态发生了“重置”,反复评估是否需要加载更多。而且更关键的是:PagedListView的默认行为是如果当前内容填不满可视区域,会自动触发fetchNextPage直到填满屏幕——这就是为什么你一打开页面就疯狂加载所有页!
第一步:把PagingState交给BLoC管理,不要在UI层临时创建
首先,修改你的FilesState,让它直接包含一个稳定的PagingState实例,而不是分开存files、hasNextPage这些零散字段:
// 定义成功状态 class PaginationSuccess extends FilesState { final PagingState<int, FilesModel> pagingState; PaginationSuccess(this.pagingState); }
然后在BLoC里初始化和更新这个pagingState,比如初始加载时:
// BLoC的初始状态或初始加载事件处理 emit(PaginationSuccess( PagingState<int, FilesModel>( pages: [initialFiles], hasNextPage: initialHasNextPage, isLoading: false, ), ));
处理FetchNextPageEvent时,正确维护状态:
on<FetchNextPageEvent>((event, emit) async { final currentState = state; if (currentState is PaginationSuccess) { final currentPagingState = currentState.pagingState; // 防重校验:正在加载或没有下一页就直接返回 if (currentPagingState.isLoading || !currentPagingState.hasNextPage) { return; } // 立即标记为加载中,阻止PagedListView重复触发 emit(PaginationSuccess( currentPagingState.copyWith(isLoading: true), )); try { // 调用API获取下一页数据 final nextPage = await _yourApi.getNextPage( page: currentPagingState.pages.length + 1, ); // 更新pages列表 final newPages = List.from(currentPagingState.pages)..add(nextPage); // 判断是否还有下一页(比如根据返回数量是否等于每页预设值) final hasNextPage = nextPage.length == 10; // 发出更新后的状态 emit(PaginationSuccess( currentPagingState.copyWith( pages: newPages, hasNextPage: hasNextPage, isLoading: false, ), )); } catch (e) { // 加载失败时重置isLoading emit(PaginationSuccess( currentPagingState.copyWith( isLoading: false, error: e, ), )); } } });
第二步:UI层直接使用BLoC传递的PagingState
修改你的BlocBuilder代码,直接复用BLoC维护的稳定状态,不要再手动创建:
child: BlocBuilder<FilesBloc, FilesState>( builder: (context, state) { if (state is PaginationSuccess) { return PagedListView<int, FilesModel>( state: state.pagingState, // 直接使用BLoC管理的状态 fetchNextPage: () { print("fetchNextPage called at ${DateTime.now()}"); context.read<FilesBloc>().add(FetchNextPageEvent()); }, scrollController: _scrollController, builderDelegate: PagedChildBuilderDelegate( animateTransitions: true, transitionDuration: const Duration(milliseconds: 500), noMoreItemsIndicatorBuilder: (context) => const Center(child: Text("No More items")), noItemsFoundIndicatorBuilder: (context) => const Text("No items"), itemBuilder: (context, item, index) { return FilesList(files: item, index: index); }, ), ); } return const SizedBox(child: Text("WRONG")); }, )
额外排查点:初始内容是否填不满视口?
如果上面的修改后还是有问题,大概率是初始列表内容太少——比如列表项高度很小,初始加载的5条内容只占了屏幕1/3,PagedListView会自动加载下一页直到填满屏幕。
如果想禁用这个预填充行为,有两个选择:
- 调整列表项的高度/内边距,让初始内容刚好填满视口
- 或者在BLoC初始状态里先把
hasNextPage设为false,直到用户第一次滚动到底部再改为true(这是临时hack,更推荐让内容自然填充)
最后:移除手动的防重flag
你之前在BLoC里加的boolean防重flag可以去掉了,只要正确维护isLoading状态,PagedListView会自动根据isLoading和hasNextPage控制是否触发fetchNextPage,不需要手动防重。
按照这个流程改完,应该就能解决一打开页面就疯狂加载的问题,fetchNextPage只会在用户滚动到接近底部时才触发啦!
内容来源于stack exchange

