Flutter 下拉刷新、上拉加载有很多第三方插件,本文使用插件为:pull_to_refresh
目前pull_to_refresh在pub.dev上的使用情况:
刷新header的类型:
ClassicHeader
const ClassicHeader({
Key? key,
RefreshStyle refreshStyle: RefreshStyle.Follow,
double height: 60.0,
Duration completeDuration: const Duration(milliseconds: 600),
this.outerBuilder,
this.textStyle: const TextStyle(color: Colors.grey),
this.releaseText,//松手加载
this.refreshingText,//正在加载
this.canTwoLevelIcon,
this.twoLevelView,
this.canTwoLevelText,
this.completeText,//加载完成
this.failedText,
this.idleText,//下拉加载
this.iconPos: IconPosition.left,
this.spacing: 15.0,
this.refreshingIcon,
this.failedIcon: const Icon(Icons.error, color: Colors.grey),
this.completeIcon: const Icon(Icons.done, color: Colors.grey),
this.idleIcon = const Icon(Icons.arrow_downward, color: Colors.grey),
this.releaseIcon = const Icon(Icons.refresh, color: Colors.grey),
})
WaterDropHeader
const WaterDropHeader({
Key? key,
this.refresh,
this.complete,
Duration completeDuration: const Duration(milliseconds: 600),
this.failed,
this.waterDropColor: Colors.grey,
this.idleIcon: const Icon(
Icons.autorenew,
size: 15,
color: Colors.white,
),
MaterialClassicHeader
const MaterialClassicHeader({
Key? key,
double height: 80.0,
this.semanticsLabel,
this.semanticsValue,
this.color,
double offset: 0,
this.distance: 50.0,
this.backgroundColor,
})
WaterDropMaterialHeader
const WaterDropMaterialHeader({
Key? key,
String? semanticsLabel,
double distance: 60.0,
double offset: 0,
String? semanticsValue,
Color color: Colors.white,
Color? backgroundColor,
})
BezierHeader
BezierHeader(
{this.child: const Text(""),
this.onOffsetChange,
this.onModeChange,
this.readyRefresh,
this.enableChildOverflow: false,
this.endRefresh,
this.onResetValue,
this.dismissType: BezierDismissType.RectSpread,
this.rectHeight: 70,
this.bezierColor})
TwoLevelHeader
const TwoLevelHeader(
{Key? key,
this.height: 80.0,
this.decoration,
this.displayAlignment: TwoLevelDisplayAlignment.fromBottom,
this.completeDuration: const Duration(milliseconds: 600),
this.textStyle: const TextStyle(color: const Color(0xff555555)),
this.releaseText,
this.refreshingText,
this.canTwoLevelIcon,
this.canTwoLevelText,
this.completeText,
this.failedText,
this.idleText,
this.iconPos: IconPosition.left,
this.spacing: 15.0,
this.refreshingIcon,
this.failedIcon: const Icon(Icons.error, color: Colors.grey),
this.completeIcon: const Icon(Icons.done, color: Colors.grey),
this.idleIcon = const Icon(Icons.arrow_downward, color: Colors.grey),
this.releaseIcon = const Icon(Icons.refresh, color: Colors.grey),
this.twoLevelWidget});
CustomHeader
const CustomHeader({
Key? key,
required this.builder, //HeaderBuilder(BuildContext context, RefreshStatus? mode);第二个参数刷新的状态,根据状态显示对应的刷新内容
this.readyToRefresh, //准备刷新的回调
this.endRefresh,//结束刷新的回调
this.onOffsetChange,//下拉距离改变的回调
this.onModeChange,//下拉状态改变的回调--RefreshStatus
this.onResetValue,
double height: 60.0,
Duration completeDuration: const Duration(milliseconds: 600),
RefreshStyle refreshStyle: RefreshStyle.Follow, //设置下拉的样式
})
刷新fotter的类型:
ClassicFooter
const ClassicFooter({
Key? key,
VoidCallback? onClick,
LoadStyle loadStyle: LoadStyle.ShowAlways,
double height: 60.0,
this.outerBuilder,
this.textStyle: const TextStyle(color: Colors.grey),
this.loadingText,
this.noDataText,
this.noMoreIcon,
this.idleText,
this.failedText,
this.canLoadingText,
this.failedIcon: const Icon(Icons.error, color: Colors.grey),
this.iconPos: IconPosition.left,
this.spacing: 15.0,
this.completeDuration: const Duration(milliseconds: 300),
this.loadingIcon,
this.canLoadingIcon: const Icon(Icons.autorenew, color: Colors.grey),
this.idleIcon = const Icon(Icons.arrow_upward, color: Colors.grey),
})
CustomFooter
const CustomFooter({
Key? key,
double height: 60.0,
this.onModeChange,
this.onOffsetChange,
this.readyLoading,
this.endLoading,
LoadStyle loadStyle: LoadStyle.ShowAlways,//下拉加载样式
required this.builder,
Function? onClick,
})
页面刷新、加载通用模块可以进行封装,封装header继承ClassicHeader,封装fotter继承ClassicFooter
class RefreshHeader extends ClassicHeader {}
class LoadingFotter extends ClassicFooter {}
代码示例
import 'package:flutter/material.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
class RefreshPage extends StatefulWidget {
RefreshPage({Key? key}) : super(key: key);
@override
_RefreshPageState createState() => _RefreshPageState();
}
class _RefreshPageState extends State<RefreshPage> {
// 定义原始数据
List<String> items = ["1", "2", "3", "4", "5", "6", "7", "8"];
// 定义刷新控制器
RefreshController _refreshController =
RefreshController(initialRefresh: false);
void _onRefresh() async {
// 模拟网络请求,此处延时1秒
await Future.delayed(Duration(milliseconds: 1000));
// 刷新成功,数据恢复原样
items = ["1", "2", "3", "4", "5", "6", "7", "8"];
if (mounted) {
setState(() {});
}
// 重置获取数据LoadStatus
_refreshController.refreshCompleted(resetFooterState: true);
}
void _onLoading() async {
// 模拟网络请求,此处延时1秒
await Future.delayed(Duration(milliseconds: 1000));
// 每次模拟加载数据,等到数据加载到20个为止,模拟数据都获取完成, 并设置LoadStatus
if (items.length >= 20) {
_refreshController.loadNoData();
return;
}
//每次加载两个数据
items.add((items.length + 1).toString());
items.add((items.length + 1).toString());
if (mounted) {
setState(() {});
}
_refreshController.loadComplete();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Scaffold'),
),
body: SmartRefresher(
enablePullDown: true, // 下拉刷新
enablePullUp: true, // 上拉加载数据
// header: ClassicHeader(
// refreshStyle: RefreshStyle.UnFollow,
// refreshingText:'正在加载...',
// releaseText: '松手加载...',
// completeText: '加载完成',
// idleText: '下拉加载',
// ),
// header: ClassicHeader(),
// header: WaterDropHeader(),
// header: MaterialClassicHeader(),
// header: WaterDropMaterialHeader(),
// header: BezierHeader(),
// header: TwoLevelHeader(),
header: CustomHeader(
refreshStyle: RefreshStyle.Behind,
builder: (BuildContext context, RefreshStatus? mode) {
Widget headerBody;
if(mode == RefreshStatus.idle) {
headerBody = Text('刷新');
}else if(mode == RefreshStatus.refreshing) {
headerBody = Text('刷新中...');
}else if(mode == RefreshStatus.failed) {
headerBody = Text('刷新失败');
}else if(mode == RefreshStatus.completed) {
headerBody = Text('刷新完成');
}else if(mode == RefreshStatus.canRefresh) {
headerBody = Text('松手刷新');
}else {
headerBody = Text("完成");
}
return Container(
height: 55.0,
child: Center(child: headerBody),
);
},
),
// footer: ClassicFooter(),
footer: CustomFooter(
// 设置上拉、下拉时的提示内容
builder: (context, mode) {
Widget body;
if (mode == LoadStatus.idle) {
body = Text("上拉加载");
} else if (mode == LoadStatus.loading) {
// body = CupertinoActivityIndicator();
body = Text("加载中");
} else if (mode == LoadStatus.failed) {
body = Text("加载失败");
} else if (mode == LoadStatus.canLoading) {
body = Text("松手加载");
} else {
body = Text("没有更多数据");
}
return Container(
height: 55.0,
child: Center(child: body),
);
},
),
controller: _refreshController,
onRefresh: _onRefresh,
onLoading: _onLoading,
child: ListView.builder(
itemBuilder: (c, i) => Card(child: Center(child: Text(items[i]))),
itemExtent: 100.0,
itemCount: items.length,
),
),
);
}
}
特殊情况:
当当前数据不满一个屏幕时,此时展示上拉加载会先加载,然后无数据提示紧跟列表后面,如果示:
当数据不足一个屏幕时,不进行上拉加载数据的操作
结果图:
代码示例:
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
class MePage extends StatefulWidget {
MePage({Key? key}) : super(key: key);
@override
_RefreshPageState createState() => _RefreshPageState();
}
class _RefreshPageState extends State<MePage> {
// 定义原始数据
List<String> items = [
"1",
"2",
];
bool isLoad = true;
// 定义刷新控制器
RefreshController _refreshController =
RefreshController(initialRefresh: false);
@override
void initState() {
super.initState();
}
void _onRefresh() async {
// 模拟网络请求,此处延时1秒
await Future.delayed(Duration(milliseconds: 1000));
// 刷新成功,数据恢复原样
items = [
"1",
"2",
"3",
"4",
"3",
"4",
"3",
"4",
];
if (mounted) {
setState(() {});
}
// 重置获取数据LoadStatus
_refreshController.refreshCompleted(resetFooterState: true);
}
void _onLoading() async {
// 模拟网络请求,此处延时1秒
await Future.delayed(Duration(milliseconds: 1000));
// 每次模拟加载数据,等到数据加载到20个为止,模拟数据都获取完成, 并设置LoadStatus
if (items.length >= 2) {
_refreshController.loadNoData();
return;
}
//每次加载两个数据
items.add((items.length + 1).toString());
if (mounted) {
setState(() {});
}
_refreshController.loadComplete();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Scaffold'),
),
body: RefreshConfiguration(
footerTriggerDistance: 80,
// dragSpeedRatio: 0.91,
hideFooterWhenNotFull: true,
headerBuilder: () => ClassicHeader(),
footerBuilder: () => ClassicFooter(),
child: SmartRefresher(
dragStartBehavior: DragStartBehavior.down,
enablePullDown: true, // 下拉刷新
enablePullUp: true, // 上拉加载数据
controller: _refreshController,
onRefresh: _onRefresh,
onLoading: _onLoading,
child: ListView.builder(
itemBuilder: (c, i) => Card(
child: Center(
child: Text(
items[i],
style: TextStyle(fontSize: 10.0),
))),
itemExtent: 100.0,
itemCount: items.length,
),
),
),
);
}
}
fotter、header的位置,可以根据需求添加位置和定制样式
RefreshConfiguration说明:
RefreshConfiguration(
{Key? key,
required this.child, //Widget
this.headerBuilder, //下拉刷新header显示内容
this.footerBuilder, //上拉加载footer显示内容
this.dragSpeedRatio: 1.0,//拖拽速度
this.shouldFooterFollowWhenNotFull, //当数据不足一个屏幕,遵循的状态
this.enableScrollWhenTwoLevel: true, 两屏时用户是否可以拖动视口
this.enableLoadingWhenNoData: false,//当footer处于nomore状态时,是否通过达到footerDistance来触发负载
this.enableBallisticRefresh: false,//是否通过BallisticScrollActivity触发刷新
this.springDescription: const SpringDescription(
mass: 2.2,
stiffness: 150,
damping: 16,
), //自定义弹簧动画
this.enableScrollWhenRefreshCompleted: false,//当刷新完成并返回时,用户是否可以拖动viewport
this.enableLoadingWhenFailed: true, //失败时,footer是否可以通过达到footerDistance来触发负载
this.twiceTriggerDistance: 150.0,//触发器twoLevel的超滚动距离
this.closeTwoLevelDistance: 80.0, //关闭二层的底部穿越距离,前提:enableScrollWhenTwoLevel为true
this.skipCanRefresh: false, //如果到达triggerDistance时需要立即刷新
this.maxOverScrollExtent, //超出边缘时的最大顶部滚动距离 --header
this.enableBallisticLoad: true,//是否通过BallisticScrollActivity触发加载
this.maxUnderScrollExtent, //超出边缘时最大底部滚动距离-- fotter
this.headerTriggerDistance: 80.0,//触发刷新的超滚距离
this.footerTriggerDistance: 15.0, //触发加载后的距离
this.hideFooterWhenNotFull: false, //当listView数据很小(不够一页)时,它应该被隐藏
this.enableRefreshVibrate: false, // 刷新 开关
this.enableLoadMoreVibrate: false,//加载 loadmore开关
this.topHitBoundary,//边界位于上边缘,当惯性滚动超过边界距离时停止
this.bottomHitBoundary,//边界位于底部边缘,当惯性在边界距离以下滚动时停止
})