基于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);
}
相关推荐
掘金安东尼6 分钟前
技术解析:高级 Excel 财务报表解析器的架构与实现
前端·javascript·面试
Hilaku7 分钟前
深入CSS层叠的本质:@layer如何优雅地解决样式覆盖与!important滥用问题
前端·css·html
天天扭码10 分钟前
AI时代,前端如何处理大模型返回的多模态数据?
前端·人工智能·面试
每天开心12 分钟前
一文教你掌握事件机制
前端·javascript·ai编程
LeeAt19 分钟前
真的!真的就一句话就能明白this指向问题
前端·javascript
阳火锅20 分钟前
都2025年了,来看看前端如何给刘亦菲加个水印吧!
前端·vue.js·面试
hahala233337 分钟前
ESLint 提交前校验技术方案
前端
程序员老刘1 小时前
Android 16开发者全解读
android·flutter·客户端
夕水1 小时前
ew-vue-component:Vue 3 动态组件渲染解决方案的使用介绍
前端·vue.js
我麻烦大了1 小时前
实现一个简单的Vue响应式
前端·vue.js