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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.23 07:36:04