在Flutter的官方SDK中给我们提供了下拉刷新的组件RefreshIndicator,但是没有提供上拉分页加载更多的组件。不过不用担心,在Flutter的ListView组件中,有一个ScrollController属性,它就是专门用来控制ListView滑动事件,在这里我们可以根据ListView的位置来判断是否滑动到了底部来做加载更多的处理。
当然,我们是可以找一些第三方的库来实现上拉加载下拉刷新的效果的,比如flutter_easyrefresh这个第三方组件,但是我并不推荐flutter_easyrefresh,因为它有一些小Bug。
所以这篇文章,我们就聊一下,如何自己去实现上拉加载下拉刷新的效果。
代码如下
import 'dart:convert'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; class SecPull extends StatefulWidget { SecPull() : super(); _RefreshPageState createState() => _RefreshPageState(); } class _RefreshPageState extends State<SecPull> { List _dataSources = []; ScrollController _scrollController = ScrollController(); int _page = 1; //请求第几页数据,用于分页请求数据 bool _haveMore = true; //是否还有更多的数据可以请求 //网络请求数据 _requestData() async { String urlStr = "http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=$_page"; var response = await Dio().get(urlStr); if (response.statusCode == 200) { setState(() { List resultList = jsonDecode(response.data)["result"]; if (this._page == 1) { //第一次加载或者下拉加载 this._dataSources = resultList; } else { //上拉刷新(将新加载的数据拼接到原来的数据数组中) this._dataSources.addAll(resultList); } this._page++; //每请求成功一次,page都要加1 /** * 这里根据当前返回的数组长度是否小于pagesize来判断接下来是否还有更多数据 * 这里的pagesize是20 */ if (resultList.length < 20) { this._haveMore = false; } }); // print(this._dataSources); } else { print("Request failed with status: ${response.statusCode}."); } } //下拉刷新 /** * 注意,这里只是给大家演示一下下拉刷新组件,所以下拉刷新的逻辑写的比较简单 * 如果真的在项目中使用的话,大家还是思考全面,不要简单拷贝如下代码! */ Future<void> _refreshData() async { await Future.delayed(Duration(seconds: 2), () { print("请求数据完成"); this._page = 1; _requestData(); }); } @override void initState() { super.initState(); //页面一加载就执行网络请求 this._requestData(); //监听滚动条的滚动事件 _scrollController.addListener(() { print(_scrollController.position.pixels); //滚动的距离 print(_scrollController.position.maxScrollExtent); //最大滚动范围 //当滚动到最底部的时候,加载新的数据 if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) { //当还有更多数据的时候才会进行加载新数据 if (this._haveMore) { this._requestData(); } } }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("refreshDemo")), body: _dataSources.length == 0 ? _loadMoreWidget() : RefreshIndicator( child: ListView.builder( controller: _scrollController, itemCount: this._dataSources.length, itemBuilder: (context, index) { /** * 当当前index等于数据源数据的长度减1的时候, * 说明当前的ListTile是最后一个ListTile, * 此时需要上拉加载新的数据,因此要在最底部显示一个加载中的圈圈 */ if (index == this._dataSources.length - 1) { return Column( children: <Widget>[ ListTile( title: Text(this._dataSources[index]["title"], maxLines: 1)), Divider(), _loadMoreWidget() ], ); } else { return Column( children: <Widget>[ ListTile( title: Text(this._dataSources[index]["title"], maxLines: 1)), Divider() ], ); } }, ), onRefresh: _refreshData, ), ); } //加载中的圈圈 Widget _loadMoreWidget() { if (this._haveMore) { //还有更多数据可以加载 return Center( child: Padding( padding: EdgeInsets.all(10), child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Text("加载中......"), CircularProgressIndicator( strokeWidth: 1, ) ], ), ), ); } else { //当没有更多数据可以加载的时候, return Center( child: Text("我是有底线的"), ); } } }
效果如下:
注:代码中已经将相关注意点做了详细说明,大家可以将该代码复制到你的工程中去直接运行,接口都是调通的。
关于我写的这个Demo,我有一点不明白,当页面滑到最底部的时候,_scrollController.position.pixels等于_scrollController.position.maxScrollExtent,此时会加载新的数据。
但是ListView是有弹簧效果的,当你把页面滚动到最底部之后,它不会立马停住,而是继续往下弹一下再返回来,也就是说,pixels会在等于maxScrollExtent之后继续往上涨(大于maxScrollExtent),然后再返回等于maxScrollExtent。
这样就会有两次_scrollController.position.pixels等于_scrollController.position.maxScrollExtent被监听到,按道理会进行两次网络请求,但是实际运行之后,我发现并没有出现我所担心的问题,我就很不解了。
各位如果有什么想法可以给我留言沟通,感谢大家。
本文分享自微信公众号 - iOS小生活(iOSHappyLife),作者:拉维
原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。