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可用的sliversEasyRefresh({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, // 首次刷新组件,不设置时使用headerthis.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可用的sliversEasyRefresh.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; // 条目总数@overrideWidget 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; // 控制结束@overridevoid 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();}@overrideWidget 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; // 条目总数@overrideWidget 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; // 条目总数@overrideWidget 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; // 条目总数@overrideWidget 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; //连接通知器@overridevoid initState() {super.initState();_headerNotifier = LinkHeaderNotifier();}@overridevoid dispose() {super.dispose();_headerNotifier.dispose();}@overrideWidget 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;});}});},);}}
// 圆形Headerclass CircleHeader extends StatefulWidget {final LinkHeaderNotifier linkNotifier;const CircleHeader(this.linkNotifier, {Key key}) : super(key: key);@overrideCircleHeaderState createState() => CircleHeaderState();}class CircleHeaderState extends State<CircleHeader> {// 指示器值double _indicatorValue = 0.0;RefreshMode get _refreshState => widget.linkNotifier.refreshState;double get _pulledExtent => widget.linkNotifier.pulledExtent;@overridevoid 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;}}});}@overrideWidget 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; // 条目总数@overrideWidget 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)
