概念
RefreshIndicator 是 Flutter 官方 Material Design 库提供的下拉刷新组件 。它封装了经典的 "swipe-to-refresh" 交互模式:当用户下拉可滚动组件(如 ListView、GridView)时,会出现一个旋转的进度指示器;如果下拉距离足够,松手后会触发一个异步刷新任务。
核心属性
Dart
1.child(类型Widget):
需要包裹的可滚动子组件,通常是 ListView 或 CustomScrollView
2.onRefresh(类型Future<void> Function()):
下拉触达阈值后执行的回调,必须返回一个 Future,完成后指示器自动消失
3.displacement(类型double)(默认值40.0):
刷新指示器停留时距离顶部(或底部)的固定偏移量,单位逻辑像素
4.edgeOffset(类型double):
指示器开始出现的偏移量,若顶部有其他固定组件可调整此值,避免遮挡
5.color(类型:Color?):
ColorScheme.primary 刷新进度指示器的前景色(即旋转圆的颜色)
6.backgroundColor(Color?):
ThemeData.canvasColor 刷新进度指示器的背景色(即圆环的底色)
7.strokeWidth(double)(2.0):
指示器圆环的线条粗细
8.triggerMode(RefreshIndicatorTriggerMode):
定义触发刷新的条件,见下文详解
RefreshIndicator 的内部并非简单监听下拉动作,而是通过一个状态机来精确管理用户手势和视觉反馈。这个设计确保了交互的流畅与准确。其核心状态流转为:
Dart
drag (拖拽):用户手指按下并向下拖拽,指示器开始显现。
armed (待触发):拖拽距离达到触发阈值,此时松手将执行刷新。
refresh (刷新中):调用 onRefresh 回调,指示器固定并旋转,等待 Future 完成。
done / canceled (完成/取消):刷新完成或拖拽未达阈值即松手,指示器执行淡出动画消失。
注意
1.child 必须是可滚动组件
// ❌ 错误:Container 不可滚动
RefreshIndicator(
onRefresh: _refreshData,
child: Container(child: Text("内容")), // 无法触发下拉
)
// ✅ 正确:包裹可滚动组件
RefreshIndicator(
onRefresh: _refreshData,
child: ListView(
children: [...],
),
)
// ✅ 正确:如果是 Column,需要用 SingleChildScrollView 包裹
RefreshIndicator(
onRefresh: _refreshData,
child: SingleChildScrollView(
child: Column(
children: [...],
),
),
)
2.内容不足时无法下拉(最常见 Bug)
// ❌ 问题代码
ListView.builder(
itemCount: items.length, // 只有2条数据,无法下拉
itemBuilder: (context, index) => ListTile(title: Text(items[index])),
)
// ✅ 解决方案1:强制可滚动
ListView.builder(
physics: const AlwaysScrollableScrollPhysics(), // ⭐ 关键
itemCount: items.length,
itemBuilder: (context, index) => ListTile(title: Text(items[index])),
)
// ✅ 解决方案2:使用 RefreshIndicator 的 displacement 参数
RefreshIndicator(
displacement: 100.0, // 增加触发距离
onRefresh: _refreshData,
child: ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
itemCount: items.length,
itemBuilder: (context, index) => ListTile(title: Text(items[index])),
),
)
3.防抖处理,防止重复刷新
// ❌ 问题代码
Future<void> _refreshData() async {
await _loadData(); // 没有防抖,可能连续触发
}
// ✅ 解决方案:加锁
bool _isRefreshing = false;
Future<void> _refreshData() async {
if (_isRefreshing) return; // ⭐ 正在刷新则忽略
_isRefreshing = true;
try {
await _loadData();
} finally {
_isRefreshing = false;
}
}
4.异步任务未完成时页面销毁
// ❌ 问题代码
Future<void> _refreshData() async {
await Future.delayed(Duration(seconds: 3));
setState(() { // ⚠️ 页面可能已销毁
items = newData;
});
}
// ✅ 解决方案:检查 mounted
Future<void> _refreshData() async {
await Future.delayed(Duration(seconds: 3));
if (!mounted) return; // ⭐ 检查页面是否还在
setState(() {
items = newData;
});
}