在Flutter中如何基于page id实现自动无限滚动分页功能
Flutter 无限自动滚动分页实现方案
以下是基于你原有代码修改后的可运行实现:
import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; // 请自行替换为你的全局变量 const mainUrl = "你的接口前缀地址/"; const accessToken = "你的授权登录token"; class Test extends StatefulWidget { Test({Key? key}) : super(key: key); @override createState() => _Test(); } class _Test extends State<Test> { // 存储已加载的全量列表数据 List<LeadFollowupss> leadList = []; String _value = 'total'; int page = 1; // 加载状态标识 bool loading = false; // 是否还有更多可加载数据 bool hasMore = true; ScrollController _scrollController = ScrollController(); @override void initState() { super.initState(); // 首次加载第一页数据 _loadData(); // 给滚动控制器添加滚动监听 _scrollController.addListener(_onScroll); } @override void dispose() { // 销毁控制器避免内存泄漏 _scrollController.dispose(); super.dispose(); } // 滚动事件监听逻辑 void _onScroll() { // 正在加载中/无更多数据时直接拦截 if (loading || !hasMore) return; // 滚动到距离底部不足200像素时触发加载下一页 if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) { _loadMoreData(); } } // 首次加载数据逻辑 Future<void> _loadData() async { setState(() { loading = true; }); try { TotalLeadsListss wrapper = TotalLeadsListss(); final result = await wrapper.getAllLeadsListss(_value, page); setState(() { leadList = result.leadFollowupss ?? []; // 可根据你接口实际每页返回条数调整阈值,返回条数少于阈值说明无更多数据 hasMore = (result.leadFollowupss?.length ?? 0) >= 10; loading = false; }); } catch (e) { setState(() { loading = false; }); debugPrint("数据加载失败:$e"); } } // 加载下一页数据逻辑 Future<void> _loadMoreData() async { setState(() { loading = true; }); try { // 页码自增 page++; TotalLeadsListss wrapper = TotalLeadsListss(); final result = await wrapper.getAllLeadsListss(_value, page); setState(() { final newList = result.leadFollowupss ?? []; leadList.addAll(newList); hasMore = newList.length >= 10; loading = false; }); } catch (e) { // 加载失败回滚页码 page--; setState(() { loading = false; }); debugPrint("更多数据加载失败:$e"); } } @override Widget build(BuildContext context) { return Scaffold( body: _buildBody(), ); } Widget _buildBody() { // 首次加载中状态 if (loading && leadList.isEmpty) { return Center(child: Image.asset("你的加载图资源路径")); } // 无数据空状态 if (leadList.isEmpty) { return Center(child: Image.asset("你的空状态图资源路径")); } return ListView.builder( controller: _scrollController, // 有更多数据时尾部多展示一个加载指示器位 itemCount: leadList.length + (hasMore ? 1 : 0), itemBuilder: (BuildContext context, int index) { // 最后一项渲染加载指示器 if (index == leadList.length) { return const Padding( padding: EdgeInsets.symmetric(vertical: 16), child: Center(child: CircularProgressIndicator()), ); } final item = leadList[index]; return Container( margin: const EdgeInsets.only(left: 8.0, right: 8.0, top: 3.0, bottom: 3.0), child: GestureDetector( onTap: () {}, child: Card( color: Colors.grey, elevation: 2.0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5.0), ), child: ListTile( contentPadding: const EdgeInsets.only(left: 8.0, right: 8.0), title: Text( item.name ?? "", maxLines: 1, overflow: TextOverflow.ellipsis, softWrap: false, style: const TextStyle(fontSize: 18.0, fontWeight: FontWeight.w600), ), subtitle: Text( item.nextMeeting.toString(), style: const TextStyle(), ), )))); }); } } /// 接口请求封装类 class TotalLeadsListss { Future<getTotalLeadsListss> getAllLeadsListss(_value, page) async { final response = await http.post(Uri.parse('${mainUrl}lead-list'), headers: { 'Accept': 'application/json', HttpHeaders.authorizationHeader: 'Bearer $accessToken', }, body: { 'type': '$_value', 'page': '$page', }); if (response.statusCode == 200) { return getTotalLeadsListss.fromJson(json.decode(response.body)); } else { throw Exception('No records found'); } } } class getTotalLeadsListss { bool? status; String? msg; List<LeadFollowupss>? leadFollowupss; getTotalLeadsListss({this.status, this.msg, this.leadFollowupss}); getTotalLeadsListss.fromJson(Map<String, dynamic> json) { status = json['status']; msg = json['message']; if (json['data'] != null) { leadFollowupss = <LeadFollowupss>[]; json['data'].forEach((v) { leadFollowupss!.add(LeadFollowupss.fromJson(v)); }); } } Map<String, dynamic> toJson() { final Map<String, dynamic> data = <String, dynamic>{}; data['status'] = status; data['message'] = msg; if (leadFollowupss != null) { data['data'] = leadFollowupss!.map((v) => v.toJson()).toList(); } return data; } } class LeadFollowupss { String? id, name, nextMeeting; LeadFollowupss({this.id, this.name, this.nextMeeting}); LeadFollowupss.fromJson(Map<String, dynamic> json) { id = json['id']; name = json['business_name']; nextMeeting = json['next_meeting']; } Map<String, dynamic> toJson() { final Map<String, dynamic> data = <String, dynamic>{}; data['id'] = id; data['business_name'] = name; data['next_meeting'] = nextMeeting; return data; } }
核心调整说明
- 新增
leadList变量存储已加载的全量数据,避免每次请求全量刷新列表 - 为滚动控制器添加监听,滚动到接近底部位置自动触发下一页加载
- 新增
hasMore变量判断是否还有剩余数据,避免无意义的重复请求 - 列表尾部新增加载指示器,优化分页加载时的用户体验
- 修复原代码语法错误,适配Flutter空安全规范
- 拆分首次加载、加载更多逻辑,代码结构更清晰易维护
注意:请根据你的接口实际每页返回条数调整
hasMore的判断阈值,比如接口每页返回20条就将判断条件改为>=20即可。
内容的提问来源于stack exchange,提问作者Abhishek Pandey
相关产品推荐
相关产品推荐

