基于EasyRefresh自定义RefreshListView

来到新公司之后,发现并没有现成的、好用的用来做分页列表数据加载的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);
}
相关推荐
IT_陈寒3 小时前
Redis内存暴涨,没想到是AOF重写捣的鬼
前端·人工智能·后端
ikoala3 小时前
让 Codex 自己查账:一个省 token 的小技巧
前端·后端·ai编程
ikoala3 小时前
AI 写页面像说明书,修 Bug 像贴膏药?把这两句话写进 AGENTS.md
前端·javascript·ai编程
好的佩奇4 小时前
Dart 之扩展方法
android·flutter·dart
去伪存真5 小时前
AI Agent Harness 深度测评与技术选型实战
前端·agent
好的佩奇6 小时前
蓝牙之经典蓝牙
android·flutter·蓝牙
FRing038 小时前
vue2干货总结7(自定义指令、插槽、单页应用程序、路由的使用(VueRouter))
开发语言·前端·vue.js·vue2
传奇开心果编程8 小时前
【Flutter踩坑排雷】第19课:路由硬编码坑|字符串路由书写错误,页面跳转失败排查
android·flutter·ios·移动开发·跨平台·ui框架·声明式ui
疯华整猫11 小时前
被杀掉之后:我把 Android 的「唤起 + 常驻」重做了一遍
前端·面试·架构
IT_陈寒13 小时前
Java开发实战:从入门到精通
前端·人工智能·后端