Flutter中如何实现ListView分页加载数据(REST API方式)
Flutter 列表分页加载实现(对接Spring Boot REST API)
下面是第二种方案的具体实现步骤,核心是滚动触发分页请求+列表增量更新+加载状态管理:
1. 准备工作:后端接口约定
先确保你的Spring Boot REST API支持分页参数,通常接口会接收两个参数:
page:当前页码(从0开始计数)size:每页数据条数(比如10)
接口返回格式建议包含:
content:当前页的数据列表last:是否是最后一页(前端用来判断是否还有更多数据)- 可选:
totalPages、totalElements辅助判断
示例接口:GET /api/data?page={page}&size={size}
2. Flutter端实现步骤
2.1 定义数据模型和API请求工具
先创建对应后端返回的数据模型,以及封装分页请求方法:
// 后端分页响应模型 class PageResponse<T> { final List<T> content; final bool last; PageResponse({required this.content, required this.last}); // 从JSON解析,这里假设你的数据模型是DataItem factory PageResponse.fromJson(List<dynamic> contentJson, bool last) { List<DataItem> content = contentJson.map((json) => DataItem.fromJson(json)).toList(); return PageResponse(content: content, last: last); } } // 单个数据项模型 class DataItem { final int id; final String title; DataItem({required this.id, required this.title}); factory DataItem.fromJson(Map<String, dynamic> json) { return DataItem(id: json['id'], title: json['title']); } } // API请求方法 Future<PageResponse<DataItem>> fetchData(int page, int size) async { final response = await http.get( Uri.parse('http://your-spring-boot-url/api/data?page=$page&size=$size'), ); if (response.statusCode == 200) { final json = jsonDecode(response.body); return PageResponse.fromJson(json['content'], json['last']); } else { throw Exception('请求失败'); } }
2.2 实现带分页的StatefulWidget
核心是维护以下状态变量:
List<DataItem> _dataList:已加载的全部数据int _currentPage:当前请求的页码bool _isLoading:是否正在加载数据(防止重复请求)bool _hasMore:是否还有更多数据可以加载
完整代码示例:
class PagedListView extends StatefulWidget { const PagedListView({super.key}); @override State<PagedListView> createState() => _PagedListViewState(); } class _PagedListViewState extends State<PagedListView> { final List<DataItem> _dataList = []; int _currentPage = 0; bool _isLoading = false; bool _hasMore = true; final _scrollController = ScrollController(); @override void initState() { super.initState(); // 初始化加载第一页 _fetchNextPage(); // 监听滚动事件,触发加载更多 _scrollController.addListener(() { // 当滚动到列表底部附近时触发加载 if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200 && !_isLoading && _hasMore) { _fetchNextPage(); } }); } Future<void> _fetchNextPage() async { if (_isLoading) return; setState(() => _isLoading = true); try { final response = await fetchData(_currentPage, 10); setState(() { _dataList.addAll(response.content); _hasMore = !response.last; _currentPage++; }); } catch (e) { // 这里可以添加错误提示,比如Toast或SnackBar ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('加载失败: $e')), ); } finally { setState(() => _isLoading = false); } } @override Widget build(BuildContext context) { return ListView.builder( controller: _scrollController, itemCount: _dataList.length + (_hasMore ? 1 : 0), // 加载指示器占一个item itemBuilder: (context, index) { // 如果是最后一个item且还有更多数据,显示加载指示器 if (index == _dataList.length) { return _isLoading ? const Padding( padding: EdgeInsets.symmetric(vertical: 16), child: Center(child: CircularProgressIndicator()), ) : const SizedBox.shrink(); // 没有加载时隐藏 } // 正常显示数据项 final item = _dataList[index]; return ListTile( title: Text(item.title), subtitle: Text('ID: ${item.id}'), ); }, ); } @override void dispose() { _scrollController.dispose(); super.dispose(); } }
3. 关键细节说明
- 防止重复请求:通过
_isLoading状态控制,请求未完成时不触发新的请求 - 滚动触发时机:设置
maxScrollExtent - 200,让用户还没完全滑到底部就开始加载,提升体验 - 无更多数据处理:当后端返回
last: true时,设置_hasMore = false,不再显示加载指示器 - 资源清理:记得在dispose里释放ScrollController,避免内存泄漏
内容的提问来源于stack exchange,提问作者ayush
相关产品推荐
相关产品推荐

