来到新公司之后,发现并没有现成的、好用的用来做分页列表数据加载的ListView
,原先项目中虽然也基于EasyRefresh
,但是很奇怪的是直接在绘制倒数第二个item
的时候就触发onLoad
方法,而且使用的时候还要继承某特定的类,既不合理又耦合严重,所以决定封装个CustomRefreshListView
,方便以后在项目中使用。
一 构建
基于EasyRefresh,使用EasyRefresh.builder
构造方法,在这里,header
和footer
可以根据项目设计自定义,不再多言。下拉刷新和上拉加载更多都回调到外部,由外部处理。若总的item
数等于itemCount
,则表明所有的数据加载完毕。代码如下:
php
@override
Widget build(BuildContext context) {
return EasyRefresh.builder(
header: widget.refreshHeader ?? MaterialHeader(),
footer: widget.refreshFooter ?? BuilderFooter(
triggerOffset: 0,
clamping: false,
builder: (BuildContext context, IndicatorState state) {
return SizedBox();
}),
onRefresh: () async {
widget.onRefresh();
},
onLoad: () async{
if (widget.totalCount == null) {
///说明不需要分页加载
logcat("无需分页加载");
return;
}
if (widget.totalCount != null && widget.totalCount == widget.itemCount) {
///说明全部item加载完毕
logcat("全部item加载完毕");
return;
}
if (_isLoading.value == false && widget.onLoad != null) {
widget.onLoad!();
_isLoading.value = true;
Future.delayed(widget.duration, () {
_isLoading.value = false; // 重置
});
}
},
childBuilder: (context, physics){
return CustomListView.builder(
itemBuilder: widget.itemBuilder,
itemCount: widget.itemCount,
isLoading: _isLoading,
// controller: _scrollController,
physics: physics,
shrinkWrap: widget.shrinkWrap,
padding: widget.padding,
);
});
}
CustomListView
的实现
如上,我们需要自定义ListView
,它继承系统的ListView
,需要传参IndexedWidgetBuilder
、itemCount
和加载中标识isLoading
,公司项目中的多绘制一个item
,最后一个item
作为footer
,大家可根据设计自行实现。代码如下:
less
class CustomListView extends ListView {
CustomListView.builder({
required IndexedWidgetBuilder itemBuilder,
required int itemCount,
required ValueNotifier<bool> isLoading,
Key? key,
ScrollController? controller,
ScrollPhysics? physics,
bool shrinkWrap = false,
EdgeInsetsGeometry? padding,
String? loadingText,
String? noMoreText,
}) : super.builder(
key: key,
itemCount: itemCount + 1,
itemBuilder: (context, index) {
if (index >= itemCount) {
return ValueListenableBuilder(
valueListenable: isLoading, builder: (context, value, child){
return isLoading.value == true ? Container(
height: 64,
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.all(6),
child: CupertinoActivityIndicator(
radius: 8,
),
),
Text(
loadingText ?? "加载中",
style: TextStyle(
color: color999999,
fontSize: 12,
),
),
],
),
) : Container(
height: 64,
alignment: Alignment.center,
margin: EdgeInsets.only(bottom: 0),
child: Text(
noMoreText ?? "无更多数据",
style: TextStyle(
color: color999999,
fontSize: 12,
),
),
);
});
}
return itemBuilder(context, index);
},
physics: physics,
shrinkWrap: shrinkWrap,
padding: padding,
controller: controller);
}
CustomRefreshListView
的使用
使用是很简单的,在onRefresh
中请求最新数据,在onLoad
中加载更多,在itemBuilder
中绘制item。如前文所述,totalCount
为可选参数,为后端返回的总item数量,不传则默认为不需要做分页处理。
scss
return CustomRefreshListView(
onRefresh: () {
viewModel.loadFirstPage();
},
onLoad: () {
viewModel.loadMore();
},
padding: EdgeInsets.only(top: 10.r),
totalCount: viewModel.riskEntity?.total,
itemCount: viewModel.listRows.length,
itemBuilder: (BuildContext context, int index) {
Model model = viewModel.listModels[index];
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
},
child: _buildItem(viewModel, model),
);
});
经过前面几步,我们就封装出了一个简单、易用、低耦合的分页加载列表,大家可根据自己实际项目中的需求修改,全部代码如下:
php
import 'package:flutter/cupertino.dart';
import 'package:easy_refresh/easy_refresh.dart';
class CustomRefreshListView extends StatefulWidget {
final Function onRefresh;
final Function? onLoad;
final Header? refreshHeader;
final Footer? refreshFooter;
final IndexedWidgetBuilder itemBuilder;
final int itemCount;
final bool shrinkWrap;
final EdgeInsetsGeometry? padding;
final Duration duration;
final String? loadingText;
final String? noMoreText;
final int? totalCount;///分页加载需要
CustomRefreshListView({
Key? key,
this.loadingText,
this.noMoreText,
this.refreshHeader,
this.refreshFooter,
this.onLoad,
this.shrinkWrap = false,
this.padding,
this.duration = const Duration(seconds:2),
required this.totalCount,
required this.onRefresh,
required this.itemCount,
required this.itemBuilder,
}) : super(key: key);
@override
_CustomRefreshListViewState createState() => _CustomRefreshListViewState();
}
class _CustomRefreshListViewState extends State<CustomRefreshListView> {
// final refreshController = EasyRefreshController();
// final ScrollController _scrollController = ScrollController();
ValueNotifier<bool> _isLoading = ValueNotifier(false);
@override
void dispose() {
super.dispose();
// refreshController.dispose();
// _scrollController.removeListener(_scrollListener);
// _scrollController.dispose();
}
@override
void initState() {
// TODO: implement initState
super.initState();
// _scrollController.addListener(_scrollListener);
}
// void _scrollListener() {
// if (_scrollController.position.userScrollDirection == ScrollDirection.reverse &&
// !_isFetching &&
// _scrollController.position.extentAfter < widget.extentAfter) {
// _isFetching = true;
// logcat('接近列表底部,触发事件');
// if (widget.onLoad != null) {
// widget.onLoad!();
// }
// Future.delayed(Duration(seconds: widget.seconds), () {
// _isFetching = false; // 重置标志
// });
// }
// }
@override
Widget build(BuildContext context) {
return EasyRefresh.builder(
header: widget.refreshHeader ?? MaterialHeader(),
footer: widget.refreshFooter ?? BuilderFooter(
triggerOffset: 0,
clamping: false,
builder: (BuildContext context, IndicatorState state) {
return SizedBox();
}),
onRefresh: () async {
widget.onRefresh();
},
onLoad: () async{
if (widget.totalCount == null) {
///说明不需要分页加载
print("无需分页加载");
return;
}
if (widget.totalCount != null && widget.totalCount == widget.itemCount) {
///说明全部item加载完毕
print("全部item加载完毕");
return;
}
if (_isLoading.value == false && widget.onLoad != null) {
widget.onLoad!();
_isLoading.value = true;
Future.delayed(widget.duration, () {
_isLoading.value = false; // 重置
});
}
},
childBuilder: (context, physics){
return CustomListView.builder(
itemBuilder: widget.itemBuilder,
itemCount: widget.itemCount,
isLoading: _isLoading,
// controller: _scrollController,
physics: physics,
shrinkWrap: widget.shrinkWrap,
padding: widget.padding,
);
});
}
}
class CustomListView extends ListView {
CustomListView.builder({
required IndexedWidgetBuilder itemBuilder,
required int itemCount,
required ValueNotifier<bool> isLoading,
Key? key,
ScrollController? controller,
ScrollPhysics? physics,
bool shrinkWrap = false,
EdgeInsetsGeometry? padding,
String? loadingText,
String? noMoreText,
}) : super.builder(
key: key,
itemCount: itemCount + 1,
itemBuilder: (context, index) {
if (index >= itemCount) {
return ValueListenableBuilder(
valueListenable: isLoading, builder: (context, value, child){
return isLoading.value == true ? Container(
height: 64,
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.all(6),
child: CupertinoActivityIndicator(
radius: 8,
),
),
Text(
loadingText ?? "加载中",
style: TextStyle(
color: Colors.black,
fontSize: 12,
),
),
],
),
) : Container(
height: 64,
alignment: Alignment.center,
margin: EdgeInsets.only(bottom: 0),
child: Text(
noMoreText ?? "无更多数据",
style: TextStyle(
color: Colors.black,
fontSize: 12,
),
),
);
});
}
return itemBuilder(context, index);
},
physics: physics,
shrinkWrap: shrinkWrap,
padding: padding,
controller: controller);
}