Flutter 中的下拉刷新和上拉加载

端木涵润
2023-12-01

在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 删除。

https://cloud.tencent.com/developer/article/1499655

 类似资料: