如何基于flutter_bloc实现无重复刷新的加载更多功能
基于flutter_bloc实现无重复刷新的加载更多功能
你的问题核心在于Bloc没有维护全局的已加载数据列表,每次加载更多只传递新页数据,同时UI层的状态处理逻辑也有问题,导致重复刷新。下面是具体的修复方案:
问题分析
- Bloc中仅在每次请求时返回当前页数据,没有保存历史数据,导致UI需要自己累加,容易出现重复添加
LoadMoreState的使用逻辑错误,UI中判断state is LoadMoreState来显示加载指示器,但状态是互斥的,发射PostLoadedState后就会覆盖之前的LoadMoreStateScrollController的listener在build方法中每次都会添加,会导致重复触发加载事件- StatelessWidget中的
posts成员会因为build重复执行而重复累加数据
修复步骤
1. 修改PostBloc,维护全局数据列表
在Bloc内部保存已加载的所有帖子,每次加载更多时追加新数据,并在状态中传递完整列表,同时添加加载状态标记避免重复请求:
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:scroool/post_events.dart'; import 'package:scroool/post_repo.dart'; import 'package:scroool/post_states.dart'; class PostsBloc extends Bloc<PostEvents, PostsStates> { final PostRepo repo; int page = 1; final ScrollController controller = ScrollController(); // 维护全局已加载的帖子列表 List<dynamic> _allPosts = []; // 标记是否正在加载更多,避免重复触发 bool _isLoadingMore = false; PostsBloc(this.repo) : super(PostInitState()) { on<FetchPosts>((event, emit) async { emit(PostLoadingState()); try { final posts = await repo.fetchPosts(page); _allPosts = posts; emit(PostLoadedState(posts: _allPosts, isLoadingMore: false)); } catch (e) { // 可以添加错误状态处理 emit(PostErrorState(message: e.toString())); } }); on<LoadMore>((event, emit) async { // 避免重复加载 if (_isLoadingMore || controller.position.pixels != controller.position.maxScrollExtent) return; _isLoadingMore = true; // 发射加载中的状态,让UI显示底部加载指示器 emit(PostLoadedState(posts: _allPosts, isLoadingMore: true)); try { page++; final newPosts = await repo.fetchPosts(page); _allPosts.addAll(newPosts); _isLoadingMore = false; emit(PostLoadedState(posts: _allPosts, isLoadingMore: false)); } catch (e) { _isLoadingMore = false; emit(PostLoadedState(posts: _allPosts, isLoadingMore: false)); // 可以添加错误提示 print('加载更多失败: $e'); } }); // 初始化listener,避免重复添加 controller.addListener(() { add(LoadMore()); }); } @override Future<void> close() { controller.dispose(); return super.close(); } }
2. 修改PostStates,添加加载状态标记
需要给PostLoadedState添加isLoadingMore字段,用来标记是否正在加载更多:
// post_states.dart示例 abstract class PostsStates {} class PostInitState extends PostsStates {} class PostLoadingState extends PostsStates {} class PostLoadedState extends PostsStates { final List<dynamic> posts; final bool isLoadingMore; PostLoadedState({required this.posts, required this.isLoadingMore}); } class PostErrorState extends PostsStates { final String message; PostErrorState({required this.message}); }
3. 修改UI层,正确处理状态
把StatelessWidget改成StatefulWidget,避免在build中重复累加数据,同时正确显示加载指示器:
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:scroool/post_bloc.dart'; import 'package:scroool/post_events.dart'; import 'package:scroool/post_states.dart'; class Scroool extends StatefulWidget { const Scroool({super.key}); @override State<Scroool> createState() => _ScrooolState(); } class _ScrooolState extends State<Scroool> { @override void initState() { super.initState(); // 初始化加载第一页数据 context.read<PostsBloc>().add(FetchPosts()); } @override Widget build(BuildContext context) { return Scaffold( body: BlocConsumer<PostsBloc, PostsStates>( listener: (context, state) {}, builder: (context, state) { if (state is PostLoadingState) { return const Center(child: CircularProgressIndicator()); } else if (state is PostLoadedState) { final posts = state.posts; return ListView.builder( controller: context.read<PostsBloc>().controller, // 如果正在加载更多,itemCount加1用来显示底部加载指示器 itemCount: state.isLoadingMore ? posts.length + 1 : posts.length, itemBuilder: (context, index) { if (index < posts.length) { final post = posts[index]; return Card( child: ListTile( leading: CircleAvatar(child: Text("${index + 1}")), title: Text(post['author'].toString()), subtitle: Text(post['title']['rendered']), ), ); } else { // 显示底部加载指示器 return const Padding( padding: EdgeInsets.symmetric(vertical: 16), child: Center(child: CircularProgressIndicator()), ); } }, ); } else if (state is PostErrorState) { return Center(child: Text(state.message)); } else { return const SizedBox(); } }, ), ); } }
关键优化点
- Bloc内部维护完整数据列表,UI直接从状态获取全部数据,避免重复累加
- 添加
_isLoadingMore标记,防止滚动到底部时重复触发加载请求 - 将
ScrollController的listener移到Bloc的初始化中,避免在build中重复添加 - 在
PostLoadedState中添加isLoadingMore字段,统一管理加载状态,UI根据该字段显示底部加载指示器
内容的提问来源于stack exchange,提问作者Osama Mohammed
相关产品推荐
相关产品推荐

