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

Flutter ListView分页加载不足limit条数时无限加载如何解决

问题根因
  • 分页终止判断逻辑完全错误:代码中拿追加新数据前的旧列表总长度和每页条数对比,而非判断本次接口实际返回的条目数,完全不符合分页逻辑的判定规则(分页终止的标准是单次请求返回的条目数小于设置的每页容量)。
  • 滚动监听和加载方法缺少双重拦截:滚动触发加载时没有判断hasMore状态,即使已经没有更多数据仍会重复发起请求;fetch方法仅判断了isLoading,没有拦截hasMore=false的场景。
  • 初始状态硬编码错误:hasMore默认写死为true,没有根据首页(首次请求)返回的实际数据量判断是否还有下一页。如果首页返回数据不足9条、且不足以撑满列表滚动区域,会直接触发滚动到底的监听,反复发起无效请求,出现加载圈常驻的问题。
  • 加载状态重置逻辑遗漏:接口请求报错、返回非200状态码的分支没有重置isLoading=false,会导致后续加载逻辑被永久拦截。
  • 状态更新顺序错误:原代码先更新page、isLoading状态,再追加列表数据,会导致状态变化时触发的滚动监听拿到错误的列表长度,重复触发请求。
修复步骤

1. 修正初始状态赋值

因为你的首页数据是提前请求存在全局MyApp中的,不要硬写hasMore = true,初始化时直接根据首页数据量判断,同时把分页大小抽成全局常量避免多处写死不一致:

// 每页大小统一常量
const int pageLimit = 9;
class _MyHomePageState extends State<MyHomePage> {
  var resultat = json.decode(MyApp.resultat)['Transactions'];
  final ScrollController controller = ScrollController();
  // 初始hasMore根据首页数据长度判断,兼容空值场景
  bool hasMore = (MyApp.rowNumber?.length ?? 0) >= pageLimit;
  int page = 2;
  bool isLoading = false;

  // 其余初始化代码...
}

2. 给滚动监听加拦截逻辑

滚动到边界时先判断是否满足加载条件,不满足直接返回,不发起请求:

@override
void initState() {
  super.initState();
  controller.addListener(() {
    // 正在加载、没有更多数据时直接拦截
    if (isLoading || !hasMore) return;
    // 滚动到底部才触发加载
    if (controller.position.maxScrollExtent == controller.offset) {
      fetch();
    }
  });
}

3. 重写fetch方法的状态逻辑

调整状态更新顺序,用本次接口返回的数据量判断是否还有下一页,补全所有分支的加载状态重置:

Future fetch() async {
  // 双重拦截,避免重复请求
  if (isLoading || !hasMore) return;
  isLoading = true;

  var token = LoginClass.resJson;
  try {
    final response = await http.post(
        Uri.parse(Config.urlReport),
        headers: <String, String>{
          'Content-Type': 'application/json; charset=UTF-8',
          "Authorization": "Bearer $token"
        },
        body: json.encode({
          "Filters": {
            "DateFrom" : MyApp.dateFrom,
            "DateTo" : MyApp.dateEnd,
            "AmountFrom": MyApp.sumFrom,
            "AmountTo": MyApp.sumTo,
            "SenderPaymentSystemId": MyApp.dropdownvalue,
            "SenderRequisite": MyApp.phoneNum
          },
          "Sorting": {
            "Field": MyApp.isCheckedFieldPicker,
            "Order": MyApp.isCheckedOrderPicker
          },
          "Pagination": {
            "PageNumber": page,
            "PageSize": pageLimit
          }
        })
    );
    if (response.statusCode == 200) {
      var reportResult = response.body;
      // 兼容接口返回Transactions为null的场景
      final List ress = json.decode(reportResult)['Transactions'] ?? [];
      setState(() {
        // 核心:用本次返回的数据长度判断是否有下一页,大于等于limit才说明可能有更多
        hasMore = ress.length >= pageLimit;
        // 追加列表数据
        MyApp.rowNumber?.addAll(ress.map<String>((item) => item['RowNumber'].toString()));
        MyApp.walletName?.addAll(ress.map<String>((item) => item['WalletName'].toString()));
        MyApp.payerPhoneNumber?.addAll(ress.map<String>((item) => item['SenderRequisite'].toString()));
        MyApp.createdDate?.addAll(ress.map<String>((item) => item['CreatedDate'].toString()));
        MyApp.amount?.addAll(ress.map<String>((item) => item['Amount'].toString()));
        // 页码自增
        page++;
        // 最后重置加载状态,避免状态提前变化触发重复监听
        isLoading = false;
      });
    } else {
      // 请求失败重置加载状态
      setState(() {
        isLoading = false;
      });
      debugPrint('请求失败,状态码:${response.statusCode}');
    }
  } catch (error) {
    // 异常分支也要重置加载状态
    setState(() {
      isLoading = false;
    });
    debugPrint('加载异常:$error');
  }
}

4. 列表构建逻辑无需大改

你现有的ListView底部指示器逻辑可以正常工作,只要hasMore状态正确,就会在数据加载完后自动显示“列表已全部加载”的提示,不会常驻加载圈。

额外注意:后续如果加下拉刷新功能,刷新时记得重置page=2、hasMore=true、清空原有列表数据再重新请求,不然会出现分页状态错乱的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 09:42:26