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也会再次经历生命周期,所以导致获取数据的逻辑会被走两遍

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

查看所有标签

猜你喜欢:

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

Mathematica Cookbook

Mathematica Cookbook

Sal Mangano / O'Reilly Media / 2009 / GBP 51.99

As the leading software application for symbolic mathematics, Mathematica is standard in many environments that rely on math, such as science, engineering, financial analysis, software development, an......一起来看看 《Mathematica Cookbook》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具