Flutter之封装一个下拉刷新上拉加载的listview

栏目: IOS · Android · 发布时间: 7年前

内容简介:在开发的过程中,经常要用到一个具有下拉刷新和上拉加载更多功能的listview ,代码的实现思路基本是差不多的。所以有必要封装一个通用的listview,方便使用。目标:外部使用BaseListView的时候,只需要传入一个页面请求的操作和item构造的方法就可以使用。将页面请求的方法定义为PageRequest,将构造子项的方法定义为ItemBuilder。 比如下面,PageRequest的返回值是列表数据的future,参数值是当前分页和每页页数。在BaseListView中定义一个 PageReq
Flutter之封装一个下拉刷新上拉加载的listview

Getting Started

1.需求场景

在开发的过程中,经常要用到一个具有下拉刷新和上拉加载更多功能的listview ,代码的实现思路基本是差不多的。所以有必要封装一个通用的listview,方便使用。

2.需要用到的控件

  1. 下拉刷新RefreshIndicator
  2. FutureBuilder:Flutter应用中的异步模型,基于与Future交互的最新快照来构建自身的widget
  3. ScrollController,可以监听listview的滑动状态
  4. typedef:在Dart语言中,方法也是对象. 使用typedef,或者function-type alias来为方法类型命名, 然后可以使用命名的方法.当把方法类型赋值给一个变量的时候,typedef保留类型信息. 具体使用方法: dart.goodev.org/guides/lang…

3.实现思路,布局方式

目标:外部使用BaseListView的时候,只需要传入一个页面请求的操作和item构造的方法就可以使用。

1. 定义typedef

将页面请求的方法定义为PageRequest,将构造子项的方法定义为ItemBuilder。 比如下面,PageRequest的返回值是列表数据的future,参数值是当前分页和每页页数。在BaseListView中定义一个 PageRequest的变量给外面赋值,然后就可以通过变量调用外部的异步操作。 ItemBuilder主要是提供给外部进行自定义构造子项,参数是数据源list和当前位置position。 根据需要可以定义更多的typedef,这里就只定义这两个。

//类型定义
typedef Future<List<T>> PageRequest<T>(int page, int pageSize);
typedef Widget ItemBuilder<T>(List<T> list, int position);
复制代码

2. FutureBuilder+RefreshIndicator实现懒加载和下拉刷新

这个之前已经实现过,可以看: github.com/LXD31256949…

3.利用ScrollController实现加载更多的功能

ListView中有一个ScrollController类型的参数,可以利用controller来监听listview的滑动状态,' 当滑动到底部的时候,可以loadmore操作

ListView({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    this.itemExtent,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    List<Widget> children = const <Widget>[],
    int semanticChildCount,
  })

复制代码

4. 一些默认的widget

  • 底部的加载菊花:当在进行loadmore操作的时候,显示底部的加载菊花,所以当在进行loadmore操作的时候, list的长度要加1,然后把菊花这个item放到最后
  • 加载数据出错的状态页面,点击可以重试
  • 加载数据为空的状态页面

4. 代码实现

/**这部分代码主要是设置滑动监听,滑动到距离底部100单位的时候,开始进行loadmore操作
    如果controller.position.pixels==controller.position.maxScrollExtent再去
    进行loadmore操作的话,实际的显示和操作会有点奇怪,所以这里设置距离底部100
    */
    controller = new ScrollController();
    controller.addListener(() {
      if (controller.position.pixels >=
          controller.position.maxScrollExtent - 100) {
        if (!isLoading) {
          isLoading = true;
          loadmore();
        }
      }
    });
复制代码
/**
   * 构造FutureBuilder
   */
FutureBuilder<List<T>> buildFutureBuilder() {
    return new FutureBuilder<List<T>>(
      builder: (context, AsyncSnapshot<List<T>> async) {
        if (async.connectionState == ConnectionState.active ||
            async.connectionState == ConnectionState.waiting) {
          isLoading = true;
          return new Center(
            child: new CircularProgressIndicator(),
          );
        }
        if (async.connectionState == ConnectionState.done) {
          isLoading = false;
          if (async.hasError) {
            //有错误的时候
            return new RetryItem(() {
              refresh();
            });
          } else if (!async.hasData) {
            //返回值为空的时候
            return new EmptyItem(() {
              refresh();
            });
          } else if (async.hasData) {
            //如果是刷新的操作
            if (widget.page == 0) {
              _list.addAll(async.data);
            }
            if (widget.total > 0 && widget.total <= _list.length) {
              widget.enableLoadmore = false;
            } else {
              widget.enableLoadmore = true;
            }

            debugPrint(
                "loadData hasData:page:${widget.page},pageSize:${widget.pageSize},list:${_list.length}");

            //计算最终的list长度
            int length = _list.length + (widget.hasHeader ? 1 : 0);

            return new RefreshIndicator(
                child: new ListView.separated(
                  physics: AlwaysScrollableScrollPhysics(),
                  controller: widget.enableLoadmore ? controller : null,
                  itemBuilder: (context, index) {
//                TODO:头部的更新,可能要放在外面,放在里面的话也行,不过要封装获取头部future的逻辑,然后提供一个外部builder给外部进行构造
//                目前需要在外面判断position是否为0去构造头部
//                if (widget.hasHeader && index == 0 && widget.header != null) {
//                  return widget.header;
//                }
                    //可以加载更多的时候,最后一个item显示菊花
                    if (widget.enableLoadmore && index == length) {
                      return new LoadMoreItem();
                    }
                    return widget.itemBuilder(_list, index);
                  },
                  itemCount: length + (widget.enableLoadmore ? 1 : 0),
                  separatorBuilder: (BuildContext context, int index) {
                    return new Divider();
                  },
                ),
                onRefresh: refresh);
          }
        }
      },
      future: future,
    );
  }
复制代码

下面是跟获取数据有关的几个方法:loadmore(),refresh(),loadData()。 loadData()会调用之前定义的页面请求PageRequest方法

Future refresh() async {
    debugPrint("loadData:refresh,list:${_list.length}");
    if (!widget.enableRefresh) {
      return;
    }
    if (isLoading) {
      return;
    }

    _list.clear();
    setState(() {
      isLoading = true;
      widget.page = 0;
      future = loadData(widget.page, widget.pageSize);
      futureBuilder = buildFutureBuilder();
    });
  }

  void loadmore() async {
    debugPrint("loadData:loadmore,list:${_list.length}");
    loadData(++widget.page, widget.pageSize).then((List<T> data) {
      setState(() {
        isLoading = false;

        _list.addAll(data);
        futureBuilder = buildFutureBuilder();
      });
    });
  }

  Future<List<T>> loadData(int page, int pageSize) async {
    debugPrint("loadData:page:$page,pageSize:$pageSize,list:${_list.length}");
    return await widget.pageRequest(page, pageSize);
  }
复制代码

5.注意的问题和踩坑

  1. 防止FutureBuilder进行不必要的重绘:这里我采用的方法,是将getData()赋值给一个future的成员变量, 用它来保存getData()的结果,以避免不必要的重绘 参考文章: blog.csdn.net/u011272795/…
  2. FutureBuilder和RefreshIndicator的嵌套问题,到底谁是谁的child,这里我是把RefreshIndicator作为FutureBuilder 的孩子。如果将RefreshIndicator放在外层,FutureBuilder作为child的话,当RefreshIndicator调用onrefreh刷新数据并用 setState()去更新界面的时候,那FutureBuilder也会再次经历生命周期,所以导致获取数据的逻辑会被走两遍

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

创京东

创京东

李志刚 / 中信出版社 / 2015-5-1 / CNY 49.80

1998年,刘强东创业,在中关村经销光磁产品。2004年,因为非典,京东偶然之下转向线上销售。2014年,京东市值已超400亿美元,跻身全球前十大互联网公司之列。 这是一个听起来很传奇的创业故事,但只有当事人了解创业维艰。 刚转向电商时,传统企业前景光明,而电商看起来前途未卜,京东如何能毅然转型并坚持到底?资金匮乏的时候,京东靠什么说服投资人?在强大的对手面前,京东靠什么反超并一路领先......一起来看看 《创京东》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

SHA 加密
SHA 加密

SHA 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具