小菜前段时间整理了两种ListView的异步加载数据时,下拉刷新与上滑加载更多的方式,每种方式都有自己的优势,网上也有很多大神讲解过ListView数据流的种种处理方式,小菜根据实际遇到的情况整理一下尝试的第三种方案。
RefreshIndicator 下拉刷新
Flutter提供了自带刷新效果的RefreshIndicator,这也是网上大神们用的最多的Widget之一,使用方式也很简单,RefreshIndicator中提供了一个刷新的回调入口onRefresh,仅需在该回调接口中处理数据请求即可,如下:
// 刷新时数据请求Future<Null> _loadRefresh() async {await Future.delayed(Duration(seconds: 2), () {setState(() {dataItems.clear();lastFileID = '0';rowNumber = 0;_getNewsData(lastFileID, rowNumber);return null;});});}// 请求接口整合数据_getNewsData(var lastID, var rowNum) async {await http.get('https://XXX.../getArticles?...&lastFileID=${lastID}&rowNumber=${rowNum}').then((response) {if (response.statusCode == 200) {var jsonRes = json.decode(response.body);newsListBean = NewsListBean(jsonRes);if (lastID == '0' && rowNum == 0 && dataItems != null) {dataItems.clear();}setState(() {if (newsListBean != null &&newsListBean.list != null &&newsListBean.list.length > 0) {for (int i = 0; i < newsListBean.list.length; i++) {dataItems.add(newsListBean.list[i]);}lastFileID = newsListBean.list[newsListBean.list.length - 1].fileID.toString();rowNumber += newsListBean.list.length;} else {}});}});}// 绑定列表数据@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("第三种加载方式"),),body: new RefreshIndicator(child: ListView.builder(itemCount: items.length,itemBuilder: buildListData(context, dataItems[index])),onRefresh: _loadRefresh, // 刷新回调));}
ScrollController 上滑动加载更多
至此,列表的下拉刷新就完成了,接下来处理【上滑加载更多】,这时我们可以借助ScrollController,用来监听列表是否滑动到底部,主要分两步:
初始化时添加监听事件,判断是否滑动到最底部;ListView中添加监听方法。
ScrollController _scrollController = new ScrollController();@overridevoid initState() {super.initState();_scrollController.addListener(() {if (_scrollController.position.pixels ==_scrollController.position.maxScrollExtent) {_getMoreData(); // 当滑到最底部时调用}});_getMoreData(); // 数据初始化}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("第三种加载方式"),),body: ListView.builder(itemCount: items.length,itemBuilder: buildListData(context, dataItems[index]),controller: _scrollController,));}
至此,列表的下拉刷新与上滑加载更多就基本完成了;接下来需要将两种合并使用,也很简单,如下:
body: new Padding(padding: EdgeInsets.all(2.0),child: RefreshIndicator(onRefresh: _loadRefresh,child: ListView.builder(itemCount: dataItems.length,physics: const AlwaysScrollableScrollPhysics(),itemBuilder: (context, index) {return buildListData(context, dataItems[index]);},controller: _scrollController,)));
Tips:注意处理好数据接口请求内容。
小优化
优化一:【上滑加载更多】添加动画效果
添加一个加载更多的布局Widget;在itemCount中将item个数+1;添加监听判断,当滑到最后一个item时展示加载更多到布局Widget。body: new Padding(padding: EdgeInsets.all(2.0),child: RefreshIndicator(onRefresh: _loadRefresh,child: ListView.builder(itemCount: dataItems.length + 1,physics: const AlwaysScrollableScrollPhysics(),itemBuilder: (context, index) {if (index == dataItems.length) {return _buildProgressIndicator();} else {return buildListData(context, dataItems[index]);}},controller: _scrollController,)));// 加载更多 WidgetWidget _buildProgressIndicator() {return new Padding(padding: EdgeInsets.fromLTRB(0.0, 14.0, 0.0, 14.0),child: new Opacity(opacity: isShowLoading ? 1.0 : 0.0,child: new Row(mainAxisAlignment: MainAxisAlignment.center,mainAxisSize: MainAxisSize.max,crossAxisAlignment: CrossAxisAlignment.center,children: <Widget>[new SpinKitChasingDots(color: Colors.blueAccent, size: 26.0),new Padding(padding: EdgeInsets.fromLTRB(10.0, 0.0, 0.0, 0.0),child: new Text('正在加载中...'))],)));}
优化二:第一次初始化加载数据时添加loading动画
RefreshIndicator中自带刷新的动画,所以小菜只是在第一次加载数据时添加一个loading动画,小菜只是填了一个小小的状态判断,如下包括异常情况下的失败页。
Widget childWidget() {Widget childWidget;if (newsListBean != null &&(newsListBean.success != null && !newsListBean.success)) {isFirstLoading = false;childWidget = new Stack(children: <Widget>[new Padding(padding: new EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 100.0),child: new Center(child: Image.asset( 'images/icon_wrong.jpg', width: 120.0, height: 120.0, ))),new Padding(padding: new EdgeInsets.fromLTRB(0.0, 100.0, 0.0, 0.0),child: new Center(child: new Text('抱歉!暂无内容哦~',style: new TextStyle(fontSize: 18.0, color: Colors.blue),)))]);} else if (dataItems != null && dataItems.length != 0) {isFirstLoading = false;childWidget = new Padding(padding: EdgeInsets.all(2.0),child: RefreshIndicator(onRefresh: _loadRefresh,child: ListView.builder(itemCount: dataItems.length + 1,physics: const AlwaysScrollableScrollPhysics(),itemBuilder: (context, index) {if (index == dataItems.length) {return _buildProgressIndicator();} else {return buildListData(context, dataItems[index]);}},controller: _scrollController,)));} else {if (isFirstLoading) { // 只有在第一次加载数据时才会展示自定义 loadingchildWidget = new Center(child: new Card(child: new Stack(children: <Widget>[new Padding(padding: new EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 35.0),child: new Center(child: SpinKitFadingCircle( color: Colors.blueAccent, size: 30.0, ))),new Padding(padding: new EdgeInsets.fromLTRB(0.0, 35.0, 0.0, 0.0),child: new Center(child: new Text('正在加载中,莫着急哦~'),))])),);} else {}}return childWidget;}
优化三:借助Future.delayed()进行延迟加载,使数据请求衔接性更好。
_getMoreData() async {if (!isShowLoading) {setState(() {isShowLoading = true;});await Future.delayed(Duration(seconds: 2), () {setState(() {_getNewsData(lastFileID, rowNumber);isShowLoading = false;return null;});});}}
小菜刚接触Flutter时间不长,还有很多不清楚和不理解的地方,如果有不对的地方还希望多多指教。