效果图



核心架构
Dart
上拉使用ScrollController
下拉使用RefreshIndicator
核心代码
Dart
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]);
},
),
),
数据流程图
Dart
┌─────────────────────────────────────────────────────────────────┐
│ 页面生命周期 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 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.首次进入页面
Dart
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.上拉加载更多
Dart
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.下拉刷新
Dart
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 自动隐藏
代码实例
Dart
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],
),
],
),
),
);
}
}
}