Flutter InfiniteListView学习与扩充

一、前言

下拉刷新上拉加载更多的ListView是移动端开发中常用的组件,Flutter中官方提供了ListView控件来显示列表,并且提供了RefreshIndicator来支持下拉刷新,但是并没有提供上拉加载更多功能的组件,通过网上查阅发现上拉加载更多的实现思路大多可分为以下两种:

1.通过ListView的ScrollController来判断列表是否滑动到最底部,如果是则加载更多。

ListView.builder(
  controller: _scrollController,
  ...
),
_scrollController.addListener(() {
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        print('滑动到了最底部');
        _getMore();
      }

2.通过数据源来控制界面渲染哪个组件,当数据源循环渲染的 index 跟数据源一样长时就渲染加载更多组件,让其显示出来,同时调用加载更多方法,获取数据,再通过state实现组件ui的更新。

ListView.builder(
  itemBuilder: _itemBuilder,
  .....
),
Widget _itemBuilder(BuildContext context, int index) {
  if (index == items.length) {
     ....
     _getMore();
     return _loadingMoreView();
  } 
}

以上两种思路都能达到上拉加载更多的目的,但是第一种思路需要进行滑动位置对比,在不同平台表现上相对应该没第二种思路好。

网上有很多封装好的ListView,但是很多都是功能不全、不易扩展或者界面显示不友好、存在bug等问题,使用起来诸多不便。最近,在Flutter中文网上学习flutter,看到了封装好的InfiniteListView组件,支持分页加载、上拉加载更多和下拉刷新,并且能自定义各种加载样式、异常样式、添加头部、分割线等,使用很方便,目前为止也没出现啥异常。
但是InfiniteListView有以下几个问题:

  • 不支持代码控制ListView的刷新,只在第一次加载时刷新和手动下拉刷新。
  • 没有相关详细使用教程,源码注释少。

为了解决以上问题,本文将对InfiniteListView的使用过程、源码理解、注意问题,以及扩充进行介绍。

二、InfiniteListView的使用

项目地址:https://github.com/flutterchina/flukit
InfiniteListView源码地址:https://github.com/flutterchina/flukit/blob/master/package_src/lib/src/infinite_listview.dart

1、引入flukit插件

在pubspec.yaml中,添加插件依赖:

dependencies:
    flukit: ^1.0.2

在Terminal中执行flutter packages get命令下载插件。

2、使用Demo:

return Scaffold(
  body: InfiniteListView<RepoBean>(
    onRetrieveData: (int page, List<RepoBean> items, bool refresh) async {
      //网络请求数据
      var data = await NetApi(context).getRepos(
          queryParameters: {
            'page': page,
            'page_size': 30,
          },
        );     
      //把请求到的新数据添加到items中
      items.addAll(data);
      // 如果接口返回的数量等于'page_size',则认为还有数据,反之则认为最后一页
      return data.length == 30;
    },
    itemBuilder: (List list, int index, BuildContext ctx) {
      // 自己去根据数据构建列表项
      return RepoItem(list[index]);
    },
  ),
);

最主要的是onRetrieveData用来获取列表需要的数据,然后添加到items中回调。itemBuilder是根据items数据来渲染每一个列表项。
如果只是使用InfiniteListView上拉加载更多、下拉刷新、分页等基础功能,那么根据demo简单调整即可,如果想实现更丰富的功能比如自定义Loading样式、扩充其他功能,需要对源码进行阅读。

三、InfiniteListView源码浅析

1、参数

在平时使用InfiniteListView,基本都是在使用以下这些参数,所以理解这些参数是非常必要的。

InfiniteListView({
  Key key,
  @required this.onRetrieveData,  //获取列表需要的数据
  @required this.itemBuilder,     //列表每一个item的样式构建 
  this.initFailBuilder,           //加载失败样式显示
  this.initLoadingBuilder,        //第一次加载列表的loading样式
  this.scrollController,          //ListView的ScrollController
  this.pageSize = 30,             //pageSize,默认每页加载30条数据
  this.loadMoreErrorViewBuilder,  //加载更多失败的样式
  this.loadingBuilder,            //加载更多的loading样式
  this.headerBuilder,             //ListView的header样式
  this.noMoreViewBuilder,         //没有更多的样式
  this.sliver = false,            //是否为Sliver组件
  this.separatorBuilder,          //分割线样式
  this.emptyBuilder,              //数据为空或者没有数据的样式
  this.initState,                 //初始化
  this.physics,                   //ScrollPhysics
}) : super(key: key);

每个参数的参数类型需要查看源码来使用,举两个例子:

1.1 separatorBuilder(分割线)的使用:

参数类型 :

typedef IndexedItemBuilder<T> = Widget Function(
    List<T> list, int index, BuildContext ctx);
final IndexedItemBuilder<T> separatorBuilder;

使用demo:

InfiniteListView<RepoBean>(
  onRetrieveData: (int page, List<RepoBean> items, bool refresh) async {
    var data = await NetApi(context).searchRepos(
      keyWords: curSearchWords,
      queryParameters: {
        'page': page,
        'page_size': 30,
      },
    );
    items.addAll(data);
    return data.length == 30;
  },
  itemBuilder: (List list, int index, BuildContext ctx) {
    return RepoItem(list[index]);
  },
  separatorBuilder: (List list,int index,BuildContext ctx){
     //当index>3时即从第五个item开始 添加分割线
    if(index>3){
      return Container(
        width: double.infinity,
        height: 10,
        color: Colors.greenAccent,
      );
    }else{
      return Container(
        width: 0,
        height: 0,
      );
    }
  },
)

效果:
从第五个item起,开始出现绿色的分割线
在这里插入图片描述

1.2 emptyBuilder(空数据样式):

参数类型:

final Widget Function(VoidCallback refresh, BuildContext context)
    emptyBuilder;

使用demo:

InfiniteListView<RepoBean>(
  emptyBuilder: (VoidCallback refresh, BuildContext context) {
    return listNoDataView(refresh, context);
  },
  onRetrieveData: (int page, List<RepoBean> items, bool refresh) async {
   .......
    },
  itemBuilder: (List list, int index, BuildContext ctx) {
   ........
  },
)

Widget listNoDataView(refresh, context) {
  return Material(
    child: InkWell(
      splashColor: Theme.of(context).secondaryHeaderColor,
      onTap: refresh,
      child: Center(
        child: Padding(
          padding: EdgeInsets.only(top: 0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Icon(
                Icons.event_busy,
                color: Theme.of(context).primaryColor,
                size: 150,
              ),
              Padding(
                  padding: EdgeInsets.only(top: 0),
                  child: Text(
                    "没有数据",
                    style: TextStyle(
                        color: Theme.of(context).primaryColor, fontSize: 22),
                  ))
            ],
          ),
        ),
      ),
    ),
  );
}

效果:
在这里插入图片描述
显示的是自定义的没有数据样式,当点击空白样式时会执行refresh方法,回调InfiniteListView源码中的refresh(bool pullDown)方法,进行列表的刷新。
其他参数就不一一详细介绍,主要根据需求查看参数的类型进行使用。

2、源码浅析

InfiniteListView的源码很简单,下面主要介绍几个比较重要的方法,在实际使用中不会用到,但是如果需要扩充其功能,或者做一些更改,就需要理解主要方法的意思。其他部分可以自己去看看。

//刷新列表,参数代表是否是下拉刷新
//一般在下拉刷新、点击加载失败或空样式、初始化列表时调用
Future<void> refresh(bool pullDown) async {
  if (state.loading) return;
  state.loading = true;
  state.noMore = false;
  refreshing = true;
  error = null;
  update();
  try {
    var _items = <T>[];
    //获取第一页数据
    var hasMore = await widget.onRetrieveData(1, _items, pullDown);
    if (_items.isEmpty ||
        _items.length % widget.pageSize != 0 && hasMore != true) {
      state.noMore = true;
    }
    state.items = _items;
    state.currentPage = 1;
  } catch (e) {
   ....
  } finally {   //结束loading
    state.loading = false;
    refreshing = false;
  }
  ......
}
//构建ListView
Widget _build() {
  //用于下拉刷新
  return RefreshIndicator(
    onRefresh: () => refresh(true),
    child: ListView.builder(
      physics: widget.physics,
      controller: widget.scrollController,
      itemCount: state.items.length + (_hasHeader ? 2 : 1),
      itemBuilder: _itemBuilder,
    ),
  );
}

//构建每一个item项
Widget _itemBuilder(BuildContext context, int index) {
  ......
  if (index == state.items.length) {
      ........
      //加载更多
      loadMore();
      return _loadingMoreView();
  } else {
    //构建item内容和分割线 
    var w = widget.itemBuilder(state.items, index, context);
    if (widget.separatorBuilder != null) {
      return Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          w,
          widget.separatorBuilder(state.items, index, context),
        ],
      );
    }
    return w;
  }
}

四、扩充

有时候InfiniteListView提供的参数不满足我们的需求,这时可以对InfiniteListView进行扩充。
例如,添加一个参数来用代码控制InfiniteListView的刷新,可以做如下扩充:

  • 1. 将InfiniteListView代码复制出来进行更改(因为源代码不属于本项目 ),换个名字例如MyInfiniteListView。
  • 2. 添加一个参数refreshKey用来控制刷新。
MyInfiniteListView({
  Key key,
  @required this.onRetrieveData,
  @required this.itemBuilder,
  ........
  this.refreshKey
});
final Key refreshKey;
  • 3. 给RefreshIndicator添加key来控制ListView的刷新。
Widget _build() {
  return RefreshIndicator(
    key: widget.refreshKey,
    onRefresh: () => refresh(true),
    child: ListView.builder(
      physics: widget.physics,
      controller: widget.scrollController,
      itemCount: state.items.length + (_hasHeader ? 2 : 1),
      itemBuilder: _itemBuilder,
    ),
  );
}

至此扩充已经完成,下面是如何去使用:

 //这个key用来在不是手动下拉,而是点击某个button或其它操作时,代码直接触发下拉刷新
final GlobalKey<RefreshIndicatorState> refreshIndicatorKey =
new GlobalKey<RefreshIndicatorState>();
......
MyInfiniteListView<UserBean>(
 //传入refreshKey参数
  refreshKey: refreshIndicatorKey,
  onRetrieveData: (int page, List<UserBean> items, bool refresh) async {
   ......
  },
  itemBuilder: (List list, int index, BuildContext ctx) {
   ......
  },
)
......
//在需要代码控制刷新的地方,使ListView刷新
refreshIndicatorKey.currentState.show();

总结InfiniteListView是一个比较好用的下拉刷新上拉加载更多的列表控件,本身支持很多自定义的样式,能很好的应用于各个列表页面。如果有其他需要的功能,可以根据需求自己去灵活的扩充。

五、 项目实战

项目地址:用flutter实现的一款界面精美的Github App
介绍:用Flutter实现的一款界面精美、功能较全、体验良好的Github客户端。支持多语言、换肤等功能。代码简单易懂且有充分的注释,很适用于学习Flutter。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值