Flutter---RefreshIndicator案例
效果图
核心架构
上拉使用ScrollController 下拉使用RefreshIndicator核心代码
body: RefreshIndicator( onRefresh: _refreshData,//下拉刷新 color: Colors.blue,//指示器箭头颜色 backgroundColor: Colors.white,//指示器背景颜色 displacement: 40.0,//触发下拉距离 child: ListView.builder( controller: _scrollController, itemCount: items.length + 1, // 始终显示加载更多组件 itemBuilder: (context, index) { if (index == items.length) { return _buildLoadMoreWidget(); } return _buildListItem(items[index]); }, ), ),数据流程图
┌─────────────────────────────────────────────────────────────────┐ │ 页面生命周期 │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 1️⃣ initState() - 页面初始化 │ │ ├── super.initState() │ │ ├── _loadData() ← 加载第一页数据 │ │ └── _scrollController.addListener() ← 监听滚动 │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 2️⃣ build() - 构建UI │ │ ├── Scaffold │ │ ├── AppBar │ │ └── RefreshIndicator │ │ └── ListView.builder │ │ ├── itemCount: items.length + 1 │ │ └── itemBuilder: │ │ ├── 最后一项 → _buildLoadMoreWidget() │ │ └── 其他项 → _buildListItem() │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 3️⃣ 等待用户交互 │ │ ├── 下拉刷新 → _refreshData() │ │ ├── 上拉加载 → _loadMoreData() │ │ └── 点击列表项 → onTap() │ └─────────────────────────────────────────────────────────────────┘数据流
1.首次进入页面
1. DemoPage 构造函数 ↓ 2. createState() → _DemoPageState() ↓ 3. _DemoPageState 构造函数 (初始化变量) • items = [] • _page = 1 • _isLoadingMore = false • _hasMore = true ↓ 4. initState() ├── super.initState() ├── _loadData() ← 调用加载方法 └── _scrollController.addListener() ↓ 5. _loadData() 执行 ├── await Future.delayed(1秒) ← 模拟网络请求 ├── 生成20条数据 └── setState() { items = newData ← 更新数据 } ↓ 6. build() 执行 (由setState触发) ├── 构建RefreshIndicator └── ListView.builder ├── itemCount = 20 + 1 = 21 ├── 索引0-19: _buildListItem() └── 索引20: _buildLoadMoreWidget() ↓ 7. 页面显示完成2.上拉加载更多
1. 用户滚动到列表底部 ↓ 2. _scrollController.addListener() 触发 ↓ 3. 判断条件: 滚动位置 ≥ 最大滚动位置 - 100 ↓ 4. _loadMoreData() 调用 ├── 检查: _isLoadingMore? _hasMore? ├── _isLoadingMore = true ├── setState(() {}) ← 显示加载状态 ├── _page++ (1→2) ├── await Future.delayed(1秒) ├── 生成新数据 (第2页) ├── setState() { │ items.addAll(newData) ← 追加数据 │ } └── finally: _isLoadingMore = false ↓ 5. build() 重新执行 ├── itemCount = 40 + 1 = 41 ├── 索引0-39: 显示所有数据 └── 索引40: 显示"上拉加载更多" (第2次加载后) ↓ 6. 用户再次滚动到底部 (第3页) ↓ 7. _loadMoreData() 再次执行 ├── _page++ (2→3) ├── 判断 _page >= 3 → _hasMore = false └── setState(() {}) ← 更新状态 ↓ 8. build() 重新执行 ├── itemCount = 40 + 1 = 41 ├── 索引40: _buildLoadMoreWidget() └── 显示"— 已加载全部 —" (不再加载)3.下拉刷新
1. 用户在列表顶部下拉 ↓ 2. RefreshIndicator 检测到手势 ↓ 3. 下拉距离 ≥ displacement (40.0) ↓ 4. onRefresh: _refreshData() 调用 ├── _page = 1 ← 重置页码 ├── _hasMore = true ← 重置标志 └── await _loadData(isRefresh: true) ↓ 5. _loadData(isRefresh: true) 执行 ├── await Future.delayed(1秒) ├── 生成20条新数据 (第1页) └── setState() { items = newData ← 替换所有数据 } ↓ 6. build() 重新执行 ├── itemCount = 20 + 1 = 21 └── 显示刷新后的数据 ↓ 7. RefreshIndicator 自动隐藏代码实例
import 'package:flutter/material.dart'; class DemoPage extends StatefulWidget { const DemoPage({super.key}); @override State<DemoPage> createState() => _DemoPageState(); } class _DemoPageState extends State<DemoPage> { List<String> items = []; //存储列表数据 int _page = 1; //当前页码 bool _isLoadingMore = false; //是否正在加载更多 bool _hasMore = true; //是否还有更多数据 final int _pageSize = 20; //每页数据量 //滚动控制器,实现上拉加载 final ScrollController _scrollController = ScrollController(); @override void initState() { super.initState(); _loadData();//加载第一页数据 //添加滚动监听 _scrollController.addListener(() { //当滚动到距离底部100像素时触发更多 if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 100) { _loadMoreData(); //加载更多数据 } }); } @override void dispose() { _scrollController.dispose(); super.dispose(); } //下拉刷新 Future<void> _refreshData() async { print("🔄 下拉刷新"); _page = 1; //重置页码 _hasMore = true; //重置标志位 await _loadData(isRefresh: true);//重新加载数据 } //加载数据 Future<void> _loadData({bool isRefresh = false}) async { try { await Future.delayed(Duration(seconds: 1)); //生成模拟数据 List<String> newData = List.generate( _pageSize, (i) => "Item ${(_page - 1) * _pageSize + i + 1}" ); //更新UI setState(() { if (isRefresh) { items = newData; //刷新替换所有数据 } else { items.addAll(newData); } }); } catch (e) { print("加载数据失败: $e"); } } //加载更多数据 Future<void> _loadMoreData() async { //防止重复加载 if (_isLoadingMore || !_hasMore) return; //设置加载状态 _isLoadingMore = true; setState(() {}); // 立即显示加载状态 _page++; //页码加1 try { await Future.delayed(Duration(seconds: 1)); // 模拟:第3页后没有更多数据 if (_page >= 3) { _hasMore = false; setState(() {}); } else { //生成数据 List<String> newData = List.generate( _pageSize, (i) => "Item ${(_page - 1) * _pageSize + i + 1}" ); //添加数据 setState(() { items.addAll(newData); }); } } catch (e) { print("加载更多失败: $e"); _page--; } finally { _isLoadingMore = false; setState(() {}); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( onPressed: () => Navigator.pop(context), icon: Icon(Icons.arrow_back_ios), ), title: Column( children: [ Text("RefreshIndicator", style: TextStyle(color: Colors.black, fontSize: 18)), Text("上拉加载,下拉刷新", style: TextStyle(color: Colors.black.withOpacity(0.7), fontSize: 12)), ], ), centerTitle: true, backgroundColor: Colors.blue, ), body: RefreshIndicator( onRefresh: _refreshData,//下拉刷新 color: Colors.blue,//指示器箭头颜色 backgroundColor: Colors.white,//指示器背景颜色 displacement: 40.0,//触发下拉距离 child: ListView.builder( controller: _scrollController, itemCount: items.length + 1, // 始终显示加载更多组件 itemBuilder: (context, index) { if (index == items.length) { return _buildLoadMoreWidget(); } return _buildListItem(items[index]); }, ), ), ); } //列表子项 Widget _buildListItem(String text) { return Card( margin: EdgeInsets.symmetric(horizontal: 16, vertical: 6), elevation: 2, child: ListTile( leading: CircleAvatar( backgroundColor: Colors.blue[100], child: Text( text.split(' ').last, style: TextStyle(color: Colors.blue[700]), ), ), //标题 title: Text( text, style: TextStyle(fontWeight: FontWeight.w500), ), //副标题 subtitle: Text("这是第${text.split(' ').last}项"), //尾部按钮 trailing: Icon(Icons.arrow_forward_ios, size: 16, color: Colors.grey), //点击事件 onTap: () { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("点击了 $text")), ); }, ), ); } // 加载更多组件 Widget _buildLoadMoreWidget() { if (_isLoadingMore) { return Container( padding: EdgeInsets.symmetric(vertical: 20), child: Center( child: Column( children: [ SizedBox( width: 24, height: 24, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.blue, ), ), SizedBox(height: 8), Text( "加载中...", style: TextStyle(color: Colors.grey[600], fontSize: 14), ), ], ), ), ); } else if (!_hasMore) { return Container( padding: EdgeInsets.symmetric(vertical: 20), child: Center( child: Text( "— 已加载全部 —", style: TextStyle(color: Colors.grey[400], fontSize: 14), ), ), ); } else { // 显示"上拉加载更多"提示 return Container( padding: EdgeInsets.symmetric(vertical: 20), child: Center( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "上拉加载更多", style: TextStyle(color: Colors.grey[500], fontSize: 13), ), SizedBox(width: 8), Icon( Icons.keyboard_arrow_up, size: 20, color: Colors.grey[500], ), ], ), ), ); } } }