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
相关产品推荐
相关产品推荐

