基于EasyRefresh自定义RefreshListView

来到新公司之后,发现并没有现成的、好用的用来做分页列表数据加载的ListView,原先项目中虽然也基于EasyRefresh,但是很奇怪的是直接在绘制倒数第二个item的时候就触发onLoad方法,而且使用的时候还要继承某特定的类,既不合理又耦合严重,所以决定封装个CustomRefreshListView,方便以后在项目中使用。

一 构建

基于EasyRefresh,使用EasyRefresh.builder构造方法,在这里,headerfooter可以根据项目设计自定义,不再多言。下拉刷新和上拉加载更多都回调到外部,由外部处理。若总的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,需要传参IndexedWidgetBuilderitemCount和加载中标识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);
}
相关推荐
陈随易1 小时前
农村程序员-关于小孩教育的思考
前端·后端·程序员
云深时现月1 小时前
jenkins使用cli发行uni-app到h5
前端·uni-app·jenkins
昨天今天明天好多天1 小时前
【Node.js]
前端·node.js
2401_857610031 小时前
深入探索React合成事件(SyntheticEvent):跨浏览器的事件处理利器
前端·javascript·react.js
雾散声声慢2 小时前
前端开发中怎么把链接转为二维码并展示?
前端
熊的猫2 小时前
DOM 规范 — MutationObserver 接口
前端·javascript·chrome·webpack·前端框架·node.js·ecmascript
天农学子2 小时前
Easyui ComboBox 数据加载完成之后过滤数据
前端·javascript·easyui
mez_Blog2 小时前
Vue之插槽(slot)
前端·javascript·vue.js·前端框架·插槽
爱睡D小猪2 小时前
vue文本高亮处理
前端·javascript·vue.js
开心工作室_kaic2 小时前
ssm102“魅力”繁峙宣传网站的设计与实现+vue(论文+源码)_kaic
前端·javascript·vue.js