Flutter中使用GetX实现ListView无限滚动:初始加载10条,滚动时API加载更多
分页滚动加载完整实现方案
第一步:补全NewsHeadlineController的分页逻辑
在现有Controller基础上新增分页状态字段、防重判断逻辑,以及专门处理后续页拉取的方法,完整实现如下:
class NewsHeadlineController extends GetxController { // 原有已实现的新闻列表 final articleList = <Article>[].obs; // 新增分页相关状态 final currentPage = 1; // 当前已加载的页码 final isLoadingMore = false.obs; // 防止重复触发加载的状态标记 final hasReachedMax = false.obs; // 判断是否已加载完全部数据 final pageSize = 10; // 和初始加载量保持一致,每页拉10条 @override void onInit() { super.onInit(); fetchArticles(); // 保留原有首批10条数据的加载逻辑 } // 保留原有首批数据拉取逻辑 Future<void> fetchArticles() async { final response = await ApiServices.getArticles(page: currentPage, pageSize: pageSize); articleList.assignAll(response.articles); // 如果返回的数据量不足单页大小,说明已到最后一页 if (response.articles.length < pageSize) { hasReachedMax.value = true; } } // 新增:滚动触发的下一页加载逻辑 Future<void> fetchNextPage() async { // 双重校验:正在加载中、已到最后一页则直接跳过 if (isLoadingMore.value || hasReachedMax.value) return; isLoadingMore.value = true; try { final nextPage = currentPage + 1; final response = await ApiServices.getArticles(page: nextPage, pageSize: pageSize); // 将新拉取的数据追加到现有列表,而非覆盖 articleList.addAll(response.articles); currentPage = nextPage; // 本次返回数据不足单页大小,标记无更多数据 if (response.articles.length < pageSize) { hasReachedMax.value = true; } } finally { // 无论请求成功或失败,都重置加载状态,支持后续重试 isLoadingMore.value = false; } } }
第二步:在NewsHeadlineScreen中接入滚动监听
原有ListView.builder的渲染逻辑保留,新增ScrollController并绑定底部检测逻辑,在用户滚动接近底部时触发Controller的分页拉取,完整实现如下:
class NewsHeadlineScreen extends StatelessWidget { const NewsHeadlineScreen({super.key}); @override Widget build(BuildContext context) { final NewsHeadlineController controller = Get.find(); final ScrollController scrollController = ScrollController(); // 给滚动控制器绑定监听,检测接近底部的时机 scrollController.addListener(() { final maxScrollExtent = scrollController.position.maxScrollExtent; final currentScrollPixels = scrollController.position.pixels; // 距离底部200像素时提前触发加载,优化用户体验 const triggerThreshold = 200.0; if (maxScrollExtent - currentScrollPixels <= triggerThreshold) { controller.fetchNextPage(); } }); return Scaffold( appBar: AppBar(title: const Text('新闻头条')), body: Obx( () => ListView.builder( controller: scrollController, padding: const EdgeInsets.all(12), // 列表总长度=现有数据长度+1(最后一个item作为加载状态提示),如果已到最大页则不展示加载提示 itemCount: controller.articleList.length + (controller.hasReachedMax.value ? 0 : 1), itemBuilder: (context, index) { // 渲染列表底部的加载中提示 if (index >= controller.articleList.length) { return const Center( child: Padding( padding: EdgeInsets.all(16), child: CircularProgressIndicator(), ), ); } // 复用你已实现的新闻条目渲染逻辑 final article = controller.articleList[index]; return NewsListItem(article: article); }, ), ), ); } }
关键踩坑注意事项
- 必须加*
isLoadingMore防重判断*:用户快速滑动、滚动越过阈值时会连续触发多次滚动监听,没有这个标记会短时间内发出多个分页请求,造成接口压力和列表数据重复。 - ScrollController的内存回收:由于页面是StatelessWidget,可将ScrollController挪到NewsHeadlineController中统一管理,在
onClose生命周期中调用dispose方法,避免页面销毁后滚动监听仍在触发无效请求。 - 不要在
itemBuilder中触发分页逻辑:itemBuilder会在列表滚动时频繁重建,在里面触发请求会导致逻辑混乱、重复触发,所有滚动相关的触发逻辑统一放在ScrollController的监听回调中。 - 短列表的自动触发兼容:当首批加载的10条数据不足一屏时,列表的maxScrollExtent会为0,此时会自动触发一次
fetchNextPage,由于Controller层的防重判断,这次触发会直接被拦截,不会产生无效请求,不需要额外做特殊处理。
内容的提问来源于stack exchange,提问作者prabin maharjan
相关产品推荐
相关产品推荐

