简介
pull_to_refresh_flutter3 是一个用于 Flutter 的第三方包,用来实现 下拉刷新(Pull-to-Refresh) 和 上拉加载更多(Load More) 的功能。它是对早期流行库 pull_to_refresh 的社区维护版本,适配了 Flutter 3。
主要特性:
- 支持 下拉刷新、上拉加载 两种模式
- 自带多种内置动画 Header / Footer 样式
- 支持自定义 Header / Footer
- 支持 ListView / GridView / CustomScrollView 等各种滚动组件
入门案例
安装
flutter pub add pull_to_refresh_flutter3
代码
import 'package:flutter/material.dart';
import 'package:pull_to_refresh_flutter3/pull_to_refresh_flutter3.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: RefreshDemoPage());
}
}
class RefreshDemoPage extends StatefulWidget {
const RefreshDemoPage({super.key});
@override
State<RefreshDemoPage> createState() => _RefreshDemoPageState();
}
class _RefreshDemoPageState extends State<RefreshDemoPage> {
final RefreshController _refreshController = RefreshController(
initialRefresh: false,
);
List<int> items = List.generate(20, (i) => i);
/// 下拉刷新
Future<void> _onRefresh() async {
await Future.delayed(const Duration(seconds: 2));
setState(() {
// 模拟数据刷新,这里重新生成了20个数据
// 实际应用中可以从网络获取最新数据
items = List.generate(20, (i) => i);
});
// 结束刷新状态
_refreshController.refreshCompleted();
}
/// 上拉加载
Future<void> _onLoading() async {
await Future.delayed(const Duration(seconds: 2));
setState(() {
// 模拟加载更多数据,这里在现有数据基础上增加10个数据
// 实际应用中可以从网络获取更多数据
items.addAll(List.generate(10, (i) => items.length + i));
});
// 结束加载状态
_refreshController.loadComplete();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('pull_to_refresh_flutter3 示例')),
// 使用 SmartRefresher 包裹 ListView
body: SmartRefresher(
// 绑定控制器
controller: _refreshController,
// 启用下拉刷新
enablePullDown: true,
// 启用上拉加载
enablePullUp: true,
// 下拉刷新和上拉加载的回调
onRefresh: _onRefresh,
onLoading: _onLoading,
// 自定义头部和底部样式
// WaterDropHeader,内置的水滴样式
header: const WaterDropHeader(), // 下拉样式
// ClassicFooter,内置的经典样式
footer: const ClassicFooter(), // 上拉样式
// ListView 列表,它可以是任何可滚动组件
// ListView.builder 是懒加载的,意味着只有可见的部分会被构建
child: ListView.builder(
itemCount: items.length,
itemBuilder: (_, i) => ListTile(title: Text('Item $i')),
),
),
);
}
}
SmartRefresher
常用属性
| 属性名 | 类型 | 说明 |
|---|---|---|
| controller | RefreshController |
必填。用于控制刷新 / 加载的状态(开始、结束、重置等) |
| child | Widget |
必填。可滚动的内容区域(如 ListView, GridView, CustomScrollView) |
| enablePullDown | bool |
是否允许下拉刷新,默认 false |
| enablePullUp | bool |
是否允许上拉加载更多,默认 false |
| onRefresh | Future<void> Function()? |
下拉刷新时触发的回调 |
| onLoading | Future<void> Function()? |
上拉加载时触发的回调 |
| header | RefreshIndicator |
自定义下拉刷新头部组件(默认 ClassicHeader) |
| footer | LoadIndicator |
自定义上拉加载尾部组件(默认 ClassicFooter) |
| physics | ScrollPhysics? |
设置滚动物理特性,默认 AlwaysScrollableScrollPhysics |
| scrollController | ScrollController? |
给内部滚动视图传入 ScrollController |
| primary | bool? |
同 ScrollView.primary,是否为主滚动区域 |
| reverse | bool |
是否反向滚动,默认 false |
| cacheExtent | double? |
同 ListView.cacheExtent,控制预加载范围 |
| dragStartBehavior | DragStartBehavior |
拖拽起始行为,默认 DragStartBehavior.start |
| keyboardDismissBehavior | ScrollViewKeyboardDismissBehavior |
控制键盘滚动隐藏行为 |
常用 Header / Footer
内置的 Header:
WaterDropHeader()(水滴动画)ClassicHeader()(经典)MaterialClassicHeader()(Material Design 风格)
内置的 Footer:
ClassicFooter()(经典)CustomFooter()(可自定义显示内容)
RefreshController
常用属性
| 属性名 | 类型 | 说明 |
|---|---|---|
| initialRefresh | bool |
构造参数,是否在初始化时就自动触发一次下拉刷新,默认 false |
| headerMode | ValueNotifier<RefreshStatus> |
当前下拉刷新头部状态 |
| footerMode | ValueNotifier<LoadStatus> |
当前上拉加载尾部状态 |
| position | ScrollPosition? |
当前绑定的滚动位置(滚动状态) |
常用方法
| 方法名 | 说明 |
|---|---|
| refreshCompleted() | 完成下拉刷新,结束刷新动画 |
| refreshFailed() | 下拉刷新失败,显示失败状态 |
| refreshToIdle() | 强制让刷新头恢复到初始状态(idle) |
| requestRefresh({Duration? duration}) | 主动触发一次下拉刷新 |
| loadComplete() | 完成上拉加载,结束加载动画 |
| loadFailed() | 上拉加载失败,显示失败状态 |
| loadNoData() | 显示「没有更多数据」状态 |
| resetNoData() | 重置「没有更多数据」状态,让上拉加载可以再次触发 |
| dispose() | 销毁控制器,一般在 State.dispose() 里调用 |
final RefreshController _refreshController = RefreshController();
Future<void> _onRefresh() async {
await Future.delayed(const Duration(seconds: 2));
// 数据刷新完成后
_refreshController.refreshCompleted();
}
Future<void> _onLoading() async {
await Future.delayed(const Duration(seconds: 2));
// 加载完成
_refreshController.loadComplete();
// 如果数据到底了
// _refreshController.loadNoData();
}
@override
void dispose() {
_refreshController.dispose();
super.dispose();
}
RefreshConfiguration
是什么
RefreshConfiguration 是一个 全局配置组件(InheritedWidget),用于:
- 统一设置 所有
SmartRefresher的默认行为、动画、阈值等 - 避免在每个
SmartRefresher上重复配置
通常把它 包裹在 MaterialApp 的外层或根页面外层。
常用属性
| 属性名 | 类型 | 说明 |
|---|---|---|
| headerBuilder | HeaderBuilder? |
全局默认下拉刷新头构建器(如 () => WaterDropHeader()) |
| footerBuilder | FooterBuilder? |
全局默认上拉加载尾构建器(如 () => ClassicFooter()) |
| headerTriggerDistance | double |
触发下拉刷新的距离阈值,默认 80 |
| footerTriggerDistance | double |
触发上拉加载的距离阈值(距离底部多少像素开始加载),默认 15.0 |
| maxOverScrollExtent | double |
最大允许下拉越界距离,默认 100.0 |
| maxUnderScrollExtent | double |
最大允许上拉越界距离,默认 0.0 |
| springDescription | SpringDescription |
弹性回弹动画参数(阻尼、质量、刚度) |
| dragSpeedRatio | double |
拖拽速度比例,默认 1.0 |
| enableScrollWhenRefreshCompleted | bool |
刷新完成后是否允许继续滚动,默认 true |
| enableLoadingWhenNoData | bool |
没有更多数据时是否还能继续触发加载,默认 false |
| hideFooterWhenNotFull | bool |
内容不足一屏时是否隐藏 footer,默认 false |
| skipCanRefresh | bool |
是否跳过 canRefresh 状态直接进入 refreshing,默认 false |
案例
import 'package:flutter/material.dart';
import 'package:pull_to_refresh_flutter3/pull_to_refresh_flutter3.dart';
void main() {
runApp(
// 全局刷新配置
RefreshConfiguration(
headerBuilder: () => const WaterDropHeader(), // 全局默认下拉头
footerBuilder: () => const ClassicFooter(), // 全局默认上拉尾
headerTriggerDistance: 80.0, // 下拉触发距离
footerTriggerDistance: 50.0, // 上拉触发距离
hideFooterWhenNotFull: false, // 内容不满一屏是否隐藏footer
enableLoadingWhenNoData: false, // 没有更多数据时禁止继续加载
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: RefreshDemoPage());
}
}
class RefreshDemoPage extends StatefulWidget {
const RefreshDemoPage({super.key});
@override
State<RefreshDemoPage> createState() => _RefreshDemoPageState();
}
class _RefreshDemoPageState extends State<RefreshDemoPage> {
final RefreshController _refreshController = RefreshController();
final List<int> _items = List.generate(20, (i) => i);
int _page = 1;
// 下拉刷新
Future<void> _onRefresh() async {
await Future.delayed(const Duration(seconds: 1));
setState(() {
_page = 1;
_items.clear();
_items.addAll(List.generate(20, (i) => i));
});
_refreshController.refreshCompleted();
// 重置「没有更多数据」状态,让上拉加载可以再次触发
// 理解这个方法需要结合 _loadNoData 方法
_refreshController.resetNoData();
}
// 上拉加载
Future<void> _onLoading() async {
await Future.delayed(const Duration(seconds: 1));
if (_page < 3) {
setState(() {
_page++;
// 模拟加载更多数据
_items.addAll(List.generate(20, (i) => i + _items.length));
});
_refreshController.loadComplete();
} else {
// 模拟没有更多数据的情况
// 当确定所有数据都加载完了,就会调用这个方法,这个时候footer 显示「没有更多数据」
// 并且不会再触发 onLoading 回调,上拉到底部也不会继续加载。
// 如果想要再次加载,需要调用 resetNoData 重置状态
_refreshController.loadNoData();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('RefreshConfiguration 示例')),
body: SmartRefresher(
controller: _refreshController,
enablePullDown: true,
enablePullUp: true,
onRefresh: _onRefresh,
onLoading: _onLoading,
child: ListView.builder(
itemCount: _items.length,
itemBuilder: (_, i) => ListTile(title: Text('Item ${_items[i]}')),
),
),
);
}
@override
void dispose() {
// 释放控制器资源
_refreshController.dispose();
super.dispose();
}
}