Flutter中点击按钮实现ListView/GridView无限加载更多数据
Flutter 实现点击加载更多 + 无限滚动列表方案
你要实现的类似生命线游戏的对话流式追加展示效果,核心是通过滚动监听+状态维护实现,ListView和GridView逻辑通用,具体实现如下:

核心实现思路
- 挂载
ScrollController监听列表滚动位置,距离底部阈值小于设定值时自动触发加载(无限滚动效果) - 列表尾部固定预留1个位置,根据加载状态展示「加载更多按钮」/「加载中指示器」/「无更多数据提示」
- 维护加载状态锁,避免重复触发数据请求
- 新数据追加完成后自动滚动到列表底部,匹配流式内容的展示体验
完整实现代码
import 'package:flutter/material.dart'; class InfiniteScrollListPage extends StatefulWidget { const InfiniteScrollListPage({super.key}); @override State<InfiniteScrollListPage> createState() => _InfiniteScrollListPageState(); } class _InfiniteScrollListPageState extends State<InfiniteScrollListPage> { // 滚动控制器 final ScrollController _scrollController = ScrollController(); // 列表数据源 final List<String> _dataList = []; // 加载状态锁 bool _isLoading = false; // 是否还有更多数据 bool _hasMore = true; // 模拟分页页码 int _page = 1; // 单页加载数量 final int _pageSize = 10; @override void initState() { super.initState(); // 初始化加载第一页数据 _loadMoreData(); // 绑定滚动监听 _scrollController.addListener(_scrollListener); } // 滚动监听,实现无限滚动 void _scrollListener() { // 距离底部200像素时触发加载,提前加载提升体验 if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200 && !_isLoading && _hasMore) { _loadMoreData(); } } // 模拟加载数据(替换为你的实际接口请求逻辑即可) Future<void> _loadMoreData() async { if (_isLoading) return; setState(() { _isLoading = true; }); // 模拟网络请求延迟 await Future.delayed(const Duration(seconds: 1)); // 模拟最多加载5页数据,实际场景根据接口返回判断是否还有更多 if (_page > 5) { setState(() { _hasMore = false; _isLoading = false; }); return; } // 生成新数据 final List<String> newData = List.generate(_pageSize, (index) => '第$_page页 第${index + 1}条内容'); setState(() { _dataList.addAll(newData); _page++; _isLoading = false; }); // 数据加载完成后滚动到底部,适配对话流场景 WidgetsBinding.instance.addPostFrameCallback((_) { if (_scrollController.hasClients) { _scrollController.animateTo( _scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); } }); } @override void dispose() { _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('无限滚动列表示例')), body: ListView.builder( controller: _scrollController, padding: const EdgeInsets.all(16), // 列表长度 = 数据长度 + 1个尾部组件 itemCount: _dataList.length + 1, itemBuilder: (context, index) { // 渲染尾部组件 if (index == _dataList.length) { return _buildFooter(); } // 渲染列表项 return ListTile( title: Text(_dataList[index]), // 适配生命线对话流的左右布局可在这里自定义item样式 ); }, ), ); } // 构建尾部组件 Widget _buildFooter() { if (!_hasMore) { return const Padding( padding: EdgeInsets.symmetric(vertical: 20), child: Center(child: Text('没有更多内容了')), ); } if (_isLoading) { return const Padding( padding: EdgeInsets.symmetric(vertical: 20), child: Center(child: CircularProgressIndicator()), ); } // 点击加载更多按钮 return Padding( padding: const EdgeInsets.symmetric(vertical: 20), child: Center( child: ElevatedButton( onPressed: _loadMoreData, child: const Text('加载更多'), ), ), ); } }
适配说明
- 如果需要用GridView实现,仅需把
ListView.builder替换为GridView.builder,其余滚动监听、数据源、尾部组件逻辑完全复用即可 - 类似生命线的对话气泡效果,只需要自定义
itemBuilder里的列表项样式,区分左右布局、气泡背景即可 - 如果使用Riverpod、Bloc等状态管理框架,只需要把数据加载、状态更新的逻辑迁移到对应状态层,视图层的滚动监听逻辑不需要改动
内容的提问来源于stack exchange,提问作者An Tran
相关产品推荐
相关产品推荐

