简介
下拉刷新,下拉加载更多,是一个比较普遍的功能。比如一个接口返回一个列表,数据有上百项,甚至更多,一次性全部返回显示不大合适。所以,一般接口会进行分页,提供一个page和pageSize参数,一小部分一小部分地来取数据。
虽然iOS原生提供这些功能,但是效果很差,一般都会求助于第三方库。Flutter也一样,这个功能也会用第三方库。
添加
flutter pub add easy_refresh
dependencies:
easy_refresh: ^3.0.2+1
链接地址
3.0是最近的一次大升级,pub上的地址都变了。以前的名字叫flutter_easy_refresh
构造方法
这个组件用的最多的是ListView。用法和手势差不多,将需要上拉下拉的ListView作为子组件。
/// 默认的构造方法
const EasyRefresh({
Key? key,
required this.child,
//... ....
})
/// 第2种构造方法
const EasyRefresh.builder({
Key? key,
required this.childBuilder,
//... ....
})
这个和ListView的构造方法很像,大多数情况下,使用默认的构造方法就可以了。
ListView嵌套的时候,就应该带builder的构造方法。physics代表ListView是否可以滑动。将需要上拉下拉的指定为可滑动,内部嵌套的指定为不可滑动。
ListView嵌套的情况,内部ListView经常会将shrinkWrap设置为true,这里不能这么做。
参数里只有一个代表子组件的child是必选的,其他的都是可选的。这个也和手势很像。但是实际使用的时候,就这一个参数其实没什么用,需要其他参数一起配合使用。
上拉下拉
这是这个组件最核心的两个功能。就像onTap对于GestureDetector组件一样。虽然是可选的,但是大多数情况,这两个参数都是要给的。
/// Refresh callback.
/// Triggered on refresh.
/// When null, disable refresh.
/// The Header current state is [IndicatorMode.processing].
/// More see [IndicatorNotifier.onTask].
final FutureOr Function()? onRefresh;
/// Load callback.
/// Triggered on load.
/// When null, disable load.
/// The Footer current state is [IndicatorMode.processing].
/// More see [IndicatorNotifier.onTask].
final FutureOr Function()? onLoad;
这里给FutureOr类型可以简单看做Future;一般数据获取都是异步的,例外的情况很少。
一般这两个方法都会调用网络数据访问的API,并且一般会有page,pageSize两个参数。
还有一个要考虑的问题就是确定是否有数据。这个在onLoad方法中需要确定。如果已经没有数据了,那么就不需要再进行网络请求了,同时也要给出相应的提示。
判断是否有更多数据一般有两种方法。一种是接口给出数据的总数,客户端端根据已经收到的数量来判断是否结束。
另外一种方法是判断本次返回的数量,与pageSize进行比较。如果还有更多数据,那么本次返回的数据量就是pageSize;如果不足的话,那么当前返回的就是最后一点数据了,是最后一个有数据的接口。(就像整除里面的求余,余数肯定比除数要小)
头和尾
在上拉和下拉过程中,在网络数据请求结束的时候,有一些状态改变,这个都是通过头和尾这两个视图来表现的。可以简单地理解为是ListView的头和尾。
这两个参数是可选的,不给的话,会提供默认的视图。大多数情况下,用默认的也可以凑合。
/// Header indicator.
final Header? header;
/// Footer indicator.
final Footer? footer;
- Header和Footer都继承自Indicator。提供了好多种Header和Footer,可以根据需要进行选择。ClassicHeader和ClassicFooter可以满足大多数情况。
控制器
和TextField组件类似,提供了一个控制器进行更多的自定义。比如,用代码进行额外的刷新,加载更多,用代码结束刷新或者加载更多等等功能。
大多数情况可以不需要用这个控制器。需要更多控制的时候,可以考虑加。
要注意的是,和TextField组件的控制器类似,控制器需要在组件外部进行初始化。用完之后,要调用dispose方法进行销毁。
如果要使用控制器,那么就要把默认的两个变量改为true
/// Take over the completion event of the refresh task.
/// Finish the refresh with [finishRefresh] and return the result.
final bool controlFinishRefresh;
/// Take over the completion event of the load task.
/// Finish the load with [finishLoad] and return the result.
final bool controlFinishLoad;
EasyRefreshController({
this.controlFinishRefresh = false,
this.controlFinishLoad = false,
});
简化的例子
- 数据逻辑部分(logic):
///下拉刷新上拉加载固定写法
List dataList = [];
int pageNum = 1;
final int pageSize = 20;
bool hadMore = true;
EasyRefreshController easyRefreshController = EasyRefreshController(
controlFinishRefresh: true,
controlFinishLoad: true,
);
/// 下拉刷新
Future refreshData() async {
pageNum = 1;
hadMore = true;
dataList = [];
getDataList();
}
/// 上拉加载更多
Future loadMoreData() async {
if (hadMore) {
pageNum++;
getDataList();
} else {
easyRefreshController.finishLoad(IndicatorResult.noMore);
}
}
/// 从网络取数据
void getDataList() {
NetworkApi.getDataListFromRemote(
pageNum: pageNum,
pageSize: pageSize,
success: (response) {
List list = response['data'];
if (list.length < pageSize) {
hadMore = false;
} else {
hadMore = true;
}
if (dataList.isEmpty) {
easyRefreshController.finishRefresh();
} else {
easyRefreshController.finishLoad(
hadMore ? IndicatorResult.success : IndicatorResult.noMore);
}
dataList.addAll(list);
update();
},
fail: (fail) {});
}
//下拉刷新上拉加载固定写法
@override
void onClose() {
easyRefreshController.dispose();
super.onClose();
}
- 界面相关部分(view)
EasyRefresh(
controller: logic.easyRefreshController,
header: ClassicHeader(),
footer: ClassicFooter,
onRefresh: logic.refreshData,
onLoad: logic.loadMoreData,
child: ListView.builder(
itemCount: logic.dataList.length,
itemBuilder: (BuildContext context, int index) {
Map item = logic.dataList[index] ?? {};
String name = item['name'] ?? '';
return Container(
height: 44.h,
child: Text(name),
);
}
),
)