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

封装一个简单的listview,下拉刷新上拉加载

956c26ab10d8e35c31a9db265a72277f.png

Getting Started

1.需求场景

在开发的过程中,经常要用到一个具有下拉刷新和上拉加载更多功能的listview

,代码的实现思路基本是差不多的。所以有必要封装一个通用的listview,方便使用。

2.需要用到的控件

下拉刷新RefreshIndicator

FutureBuilder:Flutter应用中的异步模型,基于与Future交互的最新快照来构建自身的widget

ScrollController,可以监听listview的滑动状态

typedef:在Dart语言中,方法也是对象. 使用typedef,或者function-type alias来为方法类型命名,

然后可以使用命名的方法.当把方法类型赋值给一个变量的时候,typedef保留类型信息.

具体使用方法:http://dart.goodev.org/guides/language/language-tour#typedefs

3.实现思路,布局方式

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

1. 定义typedef

将页面请求的方法定义为PageRequest,将构造子项的方法定义为ItemBuilder。

比如下面,PageRequest的返回值是列表数据的future,参数值是当前分页和每页页数。在BaseListView中定义一个

PageRequest的变量给外面赋值,然后就可以通过变量调用外部的异步操作。

ItemBuilder主要是提供给外部进行自定义构造子项,参数是数据源list和当前位置position。

根据需要可以定义更多的typedef,这里就只定义这两个。

//类型定义

typedef Future> PageRequest(int page, int pageSize);

typedef Widget ItemBuilder(List list, int position);

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

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 children = const [],

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> buildFutureBuilder() {

return new FutureBuilder>(

builder: (context, AsyncSnapshot> 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 data) {

setState(() {

isLoading = false;

_list.addAll(data);

futureBuilder = buildFutureBuilder();

});

});

}

Future> loadData(int page, int pageSize) async {

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

return await widget.pageRequest(page, pageSize);

}

5.注意的问题和踩坑

防止FutureBuilder进行不必要的重绘:这里我采用的方法,是将getData()赋值给一个future的成员变量,

用它来保存getData()的结果,以避免不必要的重绘

参考文章:https://blog.csdn.net/u011272795/article/details/83010974

FutureBuilder和RefreshIndicator的嵌套问题,到底谁是谁的child,这里我是把RefreshIndicator作为FutureBuilder

的孩子。如果将RefreshIndicator放在外层,FutureBuilder作为child的话,当RefreshIndicator调用onrefreh刷新数据并用

setState()去更新界面的时候,那FutureBuilder也会再次经历生命周期,所以导致获取数据的逻辑会被走两遍

6.下一步TODO

存在的问题:当所有数据都已请求回来后,设置不能再加载更多,这个时候会多刷新来一次页面,暂时还未解决这个问题。

继续完善这个Baselistview。

封装另外一种Baselistview,用RefreshIndicator和NotificationListener来封装就行。

7.代码地址:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值