https://pub.dev/packages/flutter_easyrefresh
中文说明:https://github.com/xuelongqy/flutter_easyrefresh/blob/v2/README.md
正如名字一样,EasyRefresh很容易就能在Flutter应用上实现下拉刷新以及上拉加载操作,它支持几乎所有的Flutter控件。它的功能与Android的SmartRefreshLayout很相似,同样也吸取了很多三方库的优点。EasyRefresh中集成了多种风格的Header和Footer,但是它并没有局限性,你可以很轻松的自定义。使用Flutter强大的动画,甚至随便一个简单的控件也可以完成。EasyRefresh的目标是为Flutter打造一个强大,稳定,成熟的下拉刷新框架。
依赖
dependencies:
flutter_easyrefresh: ^2.1.7
导入
import 'package:flutter_easyrefresh/bezier_circle_header.dart';
import 'package:flutter_easyrefresh/bezier_hour_glass_header.dart';
import 'package:flutter_easyrefresh/phoenix_footer.dart';
import 'package:flutter_easyrefresh/taurus_header.dart';
import 'package:flutter_easyrefresh/phoenix_header.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:flutter_easyrefresh/taurus_footer.dart';
import 'package:flutter_easyrefresh/material_header.dart';
import 'package:flutter_easyrefresh/delivery_header.dart';
import 'package:flutter_easyrefresh/material_footer.dart';
import 'package:flutter_easyrefresh/ball_pulse_header.dart';
import 'package:flutter_easyrefresh/bezier_bounce_footer.dart';
import 'package:flutter_easyrefresh/ball_pulse_footer.dart';
在布局文件中添加 EasyreFresh
import 'package:flutter_easyrefresh/easy_refresh.dart';
//....
// 方式一
EasyRefresh(
child: ScrollView(),
onRefresh: () async{
....
},
onLoad: () async {
....
},
)
// 方式二
EasyRefresh.custom(
slivers: <Widget>[],
onRefresh: () async{
....
},
onLoad: () async {
....
},
)
// 方式三
EasyRefresh.builder(
builder: (context, physics, header, footer) {
return CustomScrollView(
physics: physics,
slivers: <Widget>[
...
header,
...
footer,
],
);
}
onRefresh: () async{
....
},
onLoad: () async {
....
},
)
触发刷新和加载动作
EasyRefreshController _controller = EasyRefreshController();
....
EasyRefresh(
controller: _controller,
....
);
....
_controller.callRefresh();
_controller.callLoad();
控制加载和刷新完成
EasyRefreshController _controller = EasyRefreshController();
....
EasyRefresh(
enableControlFinishRefresh: true,
enableControlFinishLoad: true,
....
);
....
_controller.finishRefresh(success: true);
_controller.finishLoad(success: true, noMore: false);
使用指定的 Header 和 Footer
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:flutter_easyrefresh/material_header.dart';
import 'package:flutter_easyrefresh/material_footer.dart';
....
new EasyRefresh(
header: MaterialHeader(),
footer: MaterialFooter(),
child: ScrollView(),
....
)
添加国际化支持
不提供自带国际化支持,请自行设置ClassicalHeader和ClassicalFooter中需要展示的文字。
ClassicalHeader API
EasyRefresh(
header: ClassicalHeader(
double extent = 60.0,
double triggerDistance = 70.0,
bool float = false, //Header 浮动(Header显示在列表之上)
Duration completeDuration = const Duration(seconds: 1),
bool enableInfiniteRefresh = false, //是否开启无限加载
bool enableHapticFeedback = true, //是否触发震动反馈
bool overScroll = true,
this.key,
this.alignment, //对齐
this.refreshText, //提示刷新文字(下拉刷新)
this.refreshReadyText, //准备刷新文字(释放刷新)
this.refreshingText, //正在刷新文字(刷新中)
this.refreshedText, //刷新完成文字(刷新完成)
this.refreshFailedText, //刷新失败文字
this.noMoreText, //没有更多文字
this.showInfo: true, //是否显示额外信息(默认为时间)
this.infoText, //额外信息
this.bgColor: Colors.transparent, //背景颜色
this.textColor: Colors.black, //字体颜色
this.infoColor: Colors.teal, //额外信息字体颜色
),
),
ClassicalFooter API
footer: ClassicalFooter({
double extent = 60.0,
double triggerDistance = 70.0,
bool float = false,
Duration completeDuration = const Duration(seconds: 1),
bool enableInfiniteLoad = true, //是否开启无限加载
bool enableHapticFeedback = true, //是否触发震动反馈
bool overScroll = false, //上拉到底部时,是否继续允许往上拖拽页面(是否允许超出滚动区域)
bool safeArea = true,
EdgeInsets padding,
this.key,
this.alignment,
this.loadText, //提示加载文字
this.loadReadyText, // 准备加载文字
this.loadingText, // 正在加载文字
this.loadedText, // 加载完成文字
this.loadFailedText, // 加载失败文字
this.noMoreText,
this.showInfo: true,
this.infoText,
this.bgColor: Colors.transparent,
this.textColor: Colors.black,
this.infoColor: Colors.teal,
})
构造器API
控制器
import 'package:flutter_easyrefresh/easy_refresh.dart';
EasyRefreshController _controller;
initState(){
_controller = EasyRefreshController();
}
_controller.callRefresh(); //触发刷新
callLoad(); // 触发加载
finishRefresh(); // 完成刷新
finishLoad(); // 完成加载
resetRefreshState(); // 恢复刷新状态(用于没有更多后)
resetLoadState(); // 恢复加载状态(用于没有更多后)
_controller._easyRefreshState; // 状态
默认构造器
/// 默认构造器
/// 将child转换为CustomScrollView可用的slivers
EasyRefresh({
Key key,
this.controller, // 控制器
this.scrollController,
this.onRefresh, //刷新回调(null为不开启刷新)
this.onLoad, // 加载回调(null为不开启加载)
this.enableControlFinishRefresh = false, // 是否开启控制结束刷新
this.enableControlFinishLoad = false, // 是否开启控制结束加载
this.taskIndependence = false, // 任务独立(刷新和加载状态独立)
this.header,
this.headerIndex,
this.footer,
this.firstRefresh, // 首次刷新
this.firstRefreshWidget, // 首次刷新组件,不设置时使用header
this.emptyWidget, // 空视图,当不为null时,只会显示空视图,保留[headerIndex]以上的内容
this.topBouncing = true, // 顶部回弹(Header的overScroll属性优先,且onRefresh和header都为null时生效)
this.bottomBouncing = true, // 底部回弹(Footer的overScroll属性优先,且onLoad和footer都为null时生效)
this.behavior = const EmptyOverScrollScrollBehavior(), // 滚动行为
@required this.child,
})
custom 构造器
/// 直接使用CustomScrollView可用的slivers
EasyRefresh.custom({
...
this.listKey,
this.scrollDirection = Axis.vertical, // 列表方向
this.reverse = false, // 反向
this.scrollController,
this.primary,
this.shrinkWrap = false,
this.center,
this.anchor = 0.0,
this.cacheExtent,
this.semanticChildCount,
this.dragStartBehavior = DragStartBehavior.start,
@required this.slivers,
})
EasyRefresh.builder
EasyRefresh.builder({
Key key,
this.controller,
this.onRefresh,
this.onLoad,
this.enableControlFinishRefresh = false,
this.enableControlFinishLoad = false,
this.taskIndependence = false,
this.scrollController,
this.header,
this.footer,
this.firstRefresh,
this.topBouncing = true,
this.bottomBouncing = true,
this.behavior = const EmptyOverScrollScrollBehavior(),
@required this.builder, // 子组件构造器(final EasyRefreshChildBuilder builder;)
})
EasyRefreshChildBuilder 构造函数
typedef EasyRefreshChildBuilder = Widget Function(
BuildContext context, ScrollPhysics physics, Widget header, Widget footer);
ScrollNotificationInterceptor() 滚动通知拦截器
滚动通知拦截器(用于拦截其他UI组件的滑动事件)。
经试验,不用该组件包裹,好像也没啥问题。
import 'package:app1/utils/Base.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:flutter_swiper/flutter_swiper.dart';
class SwiperRefresh extends StatefulWidget {
@override
_SwiperRefreshState createState() => _SwiperRefreshState();
}
class _SwiperRefreshState extends State<SwiperRefresh> {
int _count = 20; // 条目总数
@override
Widget build(BuildContext context) {
return Scaffold(
body: EasyRefresh.builder(
builder: (context, physics, header, footer) {
return CustomScrollView(
slivers: [
SliverList(
delegate: SliverChildListDelegate([
Container(
height: 210,
// 滚动通知拦截器(用于拦截其他UI组件的滑动事件)
child: ScrollNotificationInterceptor(
child: Swiper(
itemCount: 5,
viewportFraction: 0.8,
scale: 0.9,
itemBuilder: (context, index) {
return Container(
height: double.infinity,
color: Color(Base.getRandomColor()),
child: Center(child: Text('swiper ${index + 1}', textScaleFactor: 2)),
);
},
),
),
),
]),
),
],
);
},
),
);
}
}
例子
控制器控制-基本例子
import 'package:app1/coms/base/empty_null/empty_null.dart';
import 'package:app1/coms/loading/ELoading.dart';
import 'package:app1/utils/Base.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
class BaseRefresh extends StatefulWidget {
@override
_BaseRefreshState createState() => _BaseRefreshState();
}
class _BaseRefreshState extends State<BaseRefresh> {
EasyRefreshController _controller;
ScrollController _scrollController;
var _count = 0; // 条目总数
bool _enableControlFinish = false; // 控制结束
@override
void initState() {
super.initState();
_controller = EasyRefreshController();
_scrollController = ScrollController();
}
getList({
isRefresh = false,
}) async {
// ELoading.show();
print('getList $isRefresh');
await Future.delayed(Duration(seconds: 2));
var temp = _count;
if (isRefresh == true) {
temp = 10;
} else {
temp += 10;
}
setState(() {
_count = temp;
});
// ELoading.destroy();
}
@override
Widget build(BuildContext context) {
return Scrollbar(
child: Container(
width: double.infinity,
height: double.infinity,
child: EasyRefresh.custom(
enableControlFinishRefresh: true, //是否开启控制结束刷新
enableControlFinishLoad: true, //是否开启控制结束加载
controller: _controller, // 控制器
scrollController: _scrollController,
header: ClassicalHeader(
enableInfiniteRefresh: false,
refreshText: '下拉刷新',
refreshReadyText: '释放刷新',
refreshingText: '刷新中...',
refreshedText: '刷新完成',
float: false, //是否浮动在列表之上
),
footer: ClassicalFooter(
enableInfiniteLoad: true, //是否开启无限加载
loadText: '上拉加载',
loadReadyText: '释放加载',
loadingText: '加载中...',
loadedText: '加载完成',
noMoreText: '没有更多了',
),
firstRefresh: true, // 是否需要首次刷新
firstRefreshWidget: Center(child: Text('页面加载中...')), // 首次刷新加载组件
emptyWidget: _count > 0 ? null : Center(child: Text('数据暂时为空')), //空视图
onRefresh: () async {
await getList(isRefresh: true);
if (mounted) {
if (!_enableControlFinish) {
_controller.resetLoadState(); //恢复加载状态
_controller.finishRefresh(); //完成刷新
}
}
},
onLoad: () async {
await getList();
if (mounted) {
if (!_enableControlFinish) {
_controller.finishLoad(noMore: _count >= 40);
}
}
},
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
color: Color(Base.getRandomColor()),
height: 100,
child: Text('我是文本 ${index + 1}', textScaleFactor: 3),
);
},
childCount: _count,
),
),
],
),
),
);
}
}
swiper 示例,解决滑动冲突
import 'package:app1/utils/Base.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:flutter_swiper/flutter_swiper.dart';
class SwiperRefresh extends StatefulWidget {
@override
_SwiperRefreshState createState() => _SwiperRefreshState();
}
class _SwiperRefreshState extends State<SwiperRefresh> {
int _count = 10; // 条目总数
@override
Widget build(BuildContext context) {
return Scaffold(
body: EasyRefresh.builder(
onRefresh: () async {
await Future.delayed(Duration(seconds: 2), () {
if (mounted) {
setState(() {
_count = 10;
});
}
});
},
onLoad: () async {
await Future.delayed(Duration(seconds: 2), () {
if (mounted) {
setState(() {
_count += 10;
});
}
});
},
builder: (context, physics, header, footer) {
return CustomScrollView(
physics: physics,
slivers: [
SliverAppBar(
pinned: true, //是否缩小到一定程度,不再缩小
expandedHeight: 100.0,
backgroundColor: Colors.red,
flexibleSpace: FlexibleSpaceBar(
title: Text('SwiperRefresh'),
centerTitle: false,
),
),
header,
footer,
SliverList(
delegate: SliverChildListDelegate([
Container(
height: 210,
// 滚动通知拦截器(用于拦截其他UI组件的滑动事件)
child: ScrollNotificationInterceptor(
child: Swiper(
itemCount: 5,
viewportFraction: 0.8,
scale: 0.9,
itemBuilder: (context, index) {
return Container(
height: double.infinity,
color: Color(Base.getRandomColor()),
child: Center(child: Text('swiper ${index + 1}', textScaleFactor: 2)),
);
},
),
),
),
]),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
color: Color(Base.getRandomColor()),
height: 100,
child: Text('我是文本 ${index + 1}', textScaleFactor: 3),
);
},
childCount: _count,
),
),
],
);
},
),
);
}
}
首次刷新例子
import 'package:app1/utils/Base.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
class FirstRefresh extends StatefulWidget {
@override
_FirstRefreshState createState() => _FirstRefreshState();
}
class _FirstRefreshState extends State<FirstRefresh> {
var _count = 0; // 条目总数
@override
Widget build(BuildContext context) {
return Scrollbar(
child: Container(
width: double.infinity,
height: double.infinity,
child: EasyRefresh.custom(
firstRefresh: true, // 是否需要首次刷新
firstRefreshWidget: Center(child: Text('页面加载中...')), // 首次刷新加载组件
onRefresh: () async {
await Future.delayed(Duration(seconds: 2), () {
if (mounted) {
setState(() {
_count = 10;
});
}
});
},
onLoad: () async {
await Future.delayed(Duration(seconds: 2), () {
if (mounted) {
setState(() {
_count += 10;
});
}
});
},
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
color: Color(Base.getRandomColor()),
height: 100,
child: Text('我是文本 ${index + 1}', textScaleFactor: 3),
);
},
childCount: _count,
),
),
],
),
),
);
}
}
空视图例子
import 'package:app1/utils/Base.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
class EmptyRefresh extends StatefulWidget {
@override
_EmptyRefreshState createState() => _EmptyRefreshState();
}
class _EmptyRefreshState extends State<EmptyRefresh> {
var _count = 0; // 条目总数
@override
Widget build(BuildContext context) {
return Scrollbar(
child: Container(
width: double.infinity,
height: double.infinity,
child: EasyRefresh.custom(
emptyWidget: _count == 0 ? Center(child: Text('暂无数据')) : null, // 无数据时展示
onRefresh: () async {
await Future.delayed(Duration(seconds: 2), () {
if (mounted) {
setState(() {
_count = 10;
});
}
});
},
onLoad: () async {
await Future.delayed(Duration(seconds: 2), () {
if (mounted) {
setState(() {
_count += 10;
});
}
});
},
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
color: Color(Base.getRandomColor()),
height: 100,
child: Text('我是文本 ${index + 1}', textScaleFactor: 3),
);
},
childCount: _count,
),
),
],
),
),
);
}
}
header连接器-例子
LinkHeader(
this.linkNotifier, {
extent = 60.0,
triggerDistance = 70.0,
completeDuration,
enableHapticFeedback = false,
})
/// 链接通知器
class LinkHeaderNotifier extends ChangeNotifier {
BuildContext context;
RefreshMode refreshState = RefreshMode.inactive; // 状态
//inactive 未开始;
//drag 拖拽中;
//armed 拖拽到足够刷新;refresh 刷新中;
//refreshed 刷新完成; done 刷新完成且动画结束
double pulledExtent = 0.0; //下拉进度
double refreshTriggerPullDistance;
double refreshIndicatorExtent;
AxisDirection axisDirection;
bool float;
Duration completeDuration;
bool enableInfiniteRefresh;
bool success = true;
bool noMore = false;
import 'package:app1/utils/Base.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
class LinkHeaderDemo extends StatefulWidget {
@override
_LinkHeaderDemoState createState() => _LinkHeaderDemoState();
}
class _LinkHeaderDemoState extends State<LinkHeaderDemo> {
int _count = 10; //总数
LinkHeaderNotifier _headerNotifier; //连接通知器
@override
void initState() {
super.initState();
_headerNotifier = LinkHeaderNotifier();
}
@override
void dispose() {
super.dispose();
_headerNotifier.dispose();
}
@override
Widget build(BuildContext context) {
return EasyRefresh.custom(
header: LinkHeader(
_headerNotifier,
extent: 70.0,
triggerDistance: 70.0,
completeDuration: Duration(milliseconds: 500),
),
slivers: [
SliverAppBar(
expandedHeight: 100.0,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
centerTitle: false,
title: Text('Header 连接器'),
),
actions: <Widget>[
CircleHeader(_headerNotifier),
],
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
color: Color(Base.getRandomColor()),
height: 100,
child: Text('我是文本 ${index + 1}', textScaleFactor: 3),
);
},
childCount: _count,
),
),
],
onRefresh: () async {
await Future.delayed(Duration(seconds: 2), () {
if (mounted) {
setState(() {
_count = 10;
});
}
});
},
onLoad: () async {
await Future.delayed(Duration(seconds: 2), () {
if (mounted) {
setState(() {
_count += 10;
});
}
});
},
);
}
}
// 圆形Header
class CircleHeader extends StatefulWidget {
final LinkHeaderNotifier linkNotifier;
const CircleHeader(this.linkNotifier, {Key key}) : super(key: key);
@override
CircleHeaderState createState() => CircleHeaderState();
}
class CircleHeaderState extends State<CircleHeader> {
// 指示器值
double _indicatorValue = 0.0;
RefreshMode get _refreshState => widget.linkNotifier.refreshState;
double get _pulledExtent => widget.linkNotifier.pulledExtent;
@override
void initState() {
super.initState();
//下拉进度监听
widget.linkNotifier.addListener(onLinkNotify);
}
//下拉进度监听-回调
void onLinkNotify() {
setState(() {
if (_refreshState == RefreshMode.armed || _refreshState == RefreshMode.refresh) {
_indicatorValue = null;
} else if (_refreshState == RefreshMode.refreshed || _refreshState == RefreshMode.done) {
_indicatorValue = 1.0;
} else {
if (_refreshState == RefreshMode.inactive) {
_indicatorValue = 0.0;
} else {
double indicatorValue = _pulledExtent / 70.0 * 0.8;
_indicatorValue = indicatorValue < 0.8 ? indicatorValue : 0.8;
}
}
});
}
@override
Widget build(BuildContext context) {
return Center(
child: Container(
alignment: Alignment.center,
margin: EdgeInsets.only(
right: 20.0,
),
width: 24.0,
height: 24.0,
child: CircularProgressIndicator(
value: _indicatorValue,
valueColor: AlwaysStoppedAnimation(Colors.black),
strokeWidth: 2.4,
),
),
);
}
}
extended_nested_scroll_view tab列表示例
https://www.yuque.com/zhuchaoyang/tnyvrp/vz7sxb
样式例子
Materail 质感设计,android样式
import 'package:app1/utils/Base.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
class StyleMaterial extends StatefulWidget {
@override
_StyleMaterialState createState() => _StyleMaterialState();
}
class _StyleMaterialState extends State<StyleMaterial> {
var _count = 10; // 条目总数
@override
Widget build(BuildContext context) {
return Scrollbar(
child: EasyRefresh.custom(
header: MaterialHeader(),
footer: MaterialFooter(),
onRefresh: () async {
await Future.delayed(Duration(seconds: 2), () {
if (mounted) {
setState(() {
_count = 10;
});
}
});
},
onLoad: () async {
await Future.delayed(Duration(seconds: 2), () {
if (mounted) {
setState(() {
_count += 10;
});
}
});
},
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
color: Color(Base.getRandomColor()),
height: 100,
child: Text('我是文本 ${index + 1}', textScaleFactor: 3),
);
},
childCount: _count,
),
),
],
),
);
}
}
球脉冲效果
文件: loading.zip
EasyRefresh.custom(
header: MyLoading.pullDownLoading(),
footer: MyLoading.pullUpLoading(),
)
弹出圆圈
EasyRefresh.custom(
header: BezierCircleHeader(),
footer: BezierBounceFooter(),
)
弹出HourGlass
EasyRefresh.custom(
header: BezierHourGlassHeader(
color: Theme.of(context).scaffoldBackgroundColor,
),
footer: BezierBounceFooter(
color: Theme.of(context).scaffoldBackgroundColor,
),
)
冲上云霄
EasyRefresh.custom(
header: TaurusHeader(),
footer: TaurusFooter(),
)
金色校园
EasyRefresh.custom(
header: PhoenixHeader(),
footer: PhoenixFooter(),
)
气球快递
EasyRefresh.custom(
header: DeliveryHeader(
backgroundColor: Colors.grey[100],
),
)
星空
EasyRefresh.custom(
header: SpaceHeader(),
)
小黄人
EasyRefresh.custom(
BobMinionHeader(animation: 'Jump'), //Stand Dance Jump Wave
)