01_Flutter之下拉刷新和上拉加载

news2025/2/22 13:22:30

一.创建页面

由于我们需要请求网络,并将返回的数据渲染到页面上,所以需要继承StatefulWidget,本文涉及的接口,取自鸿神的玩android开放API

class ProjectListPage extends StatefulWidget {
  
  State<StatefulWidget> createState() => _ProjectListPageState();
}

class _ProjectListPageState extends State<ProjectListPage> {

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("项目列表")
      ),
      body: Container()
    );
  }

}

二.使用FutureBuilder异步初始化页面数据

通过FutureBuilder,我们可以从互联网上获取数据的过程中显示一个加载框,等获取数据成功时再渲染页面,本文的重点不是讲FutureBuilder怎么使用,就不做过多解释了,直接上代码:

class ProjectListPage extends StatefulWidget {
  
  State<StatefulWidget> createState() => _ProjectListPageState();
}

class _ProjectListPageState extends State<ProjectListPage> {

  late Future<PageModel<ProjectModel>> future;

  
  void initState() {
    // TODO: implement initState
    super.initState();
    future = IndexDao.getProjectList(cid: 0, start: 1);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("项目列表")
      ),
      body: FutureBuilder<PageModel<ProjectModel>>(
        future: future,
        builder: (BuildContext context, AsyncSnapshot<PageModel<ProjectModel>> snapshot) {
          if (snapshot.connectionState != ConnectionState.done) {
            //请求中,显示加载圈
            return const Center(
              child: SizedBox(
                width: 30,
                height: 30,
                child: CircularProgressIndicator(),
              ),
            );
          } else {
            //请求结束
            if (snapshot.hasError) {
            // 请求失败,显示错误
              return Text("Error: ${snapshot.error}");
            } else {
              // 请求成功,显示数据
              return Text("data: ${snapshot.data}");
            }
          }
        },
      )
    );
  }

}

在这里插入图片描述

三.渲染列表

if (snapshot.hasError) {
  // 请求失败,显示错误
  return Text("Error: ${snapshot.error}");
} else {
  // 请求成功,显示数据
  List<ProjectModel> datas = snapshot.data?.records ?? [];
  return ListView.separated(
    padding: EdgeInsets.all(10),
    itemBuilder: (BuildContext context, int index) {
      return Container(
        padding: const EdgeInsets.all(10),
        decoration: const BoxDecoration(
          borderRadius: BorderRadius.all(Radius.circular(5)),
          color: Colors.white,
        ),
        child: IntrinsicHeight(
          child: Row(
            mainAxisSize: MainAxisSize.max,
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              SizedBox(
                width: 120,
                height: 1,
                child: Image.network(datas[index].envelopePic ?? "", fit: BoxFit.cover),
              ),
              SizedBox(width: 10,),
              Expanded(
                flex: 1,
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.stretch,

                  children: [
                    Text(
                      "${datas[index]?.title}",
                      maxLines: 2,
                      style: const TextStyle(
                        overflow: TextOverflow.ellipsis,
                        fontSize: 16
                      ),
                    ),

                    const SizedBox(
                      height: 10,
                    ),

                    Text(
                      "${datas[index]?.desc}",
                      maxLines: 2,
                      style: const TextStyle(
                        overflow: TextOverflow.ellipsis,
                        fontSize: 14
                      )
                    ),
                  ],
                )
              )
            ],
          ),
        ),
      );
    },
    separatorBuilder: (BuildContext context, int index) {
      return const Divider(color: Colors.transparent, height: 10,);
    },
    itemCount: datas.length
  );
}

在这里插入图片描述

四.实现下拉刷新

直接使用Flutter内置的RefreshIndicator实现下拉刷新

int start = 1;

RefreshIndicator(
  onRefresh: () {
    return _refreshData();
  },
  child: ListView.separated(...)
);
  
Future<void> _refreshData() {
  start = 1;

  return IndexDao.getProjectList(cid: 0, start: start).then((value) {
    setState(() {
      datas.clear();
      datas.addAll(value.records);
    });
  });
}

在这里插入图片描述

五.上拉加载更多

重点来了,我们应该在何时去加载更多数据呢?那自然是ListView滑动到底部的时候。可以通过ScrollController监听

late ScrollController _controller;


void initState() {
  // TODO: implement initState
  super.initState();
  future = IndexDao.getProjectList(cid: 0, start: 1);
  _controller = ScrollController();
  _controller.addListener(() {
    if(_controller.position.extentAfter == 0) {
      //划动到底部了,加载更多数据
      print("划动到底部了,加载更多数据");
    }
  });
}

Widget build(BuildContext context) {
  ...
  return RefreshIndicator(
    onRefresh: () {
      return _refreshData();
    },
    child: ListView.separated(
    	controller: _controller,
    	...
  	)
  );
}

也可以使用NotificationListener监听

late ScrollController _controller;

void initState() {
  // TODO: implement initState
  super.initState();
  future = IndexDao.getProjectList(cid: 0, start: 1);
  _controller = ScrollController();
}

Widget build(BuildContext context) {
  return NotificationListener<ScrollEndNotification>(
    onNotification: (ScrollEndNotification notification) {
      if (_controller.position.extentAfter == 0) {
      	//滚动到底部
      	//加载更多数据
      }
      return false;
    },
    child: RefreshIndicator(
      onRefresh: () {
        return _refreshData();
      },
    	child: ListView.separated(
    		controller: _controller,
    		...
  		)
  	)
  )
}

加载更多数据,分别对应四种加载状态,more:有更多数据,loading: 加载中,noMore: 没有更多数据了,error: 请求网络出错了

enum LoadMoreStatus { more, loading, error, noMore }

我们需要根据这四种加载状态,显示不同的footer,并且,ListView的itemCount需要在原有基础上加一,预留出一个位置,显示Footer

ListView.separated(
  ...
  itemBuilder: (BuildContext context, int index) {
    if(index == datas.length) {
      if(loadMoreStatus == LoadMoreStatus.more) {
        return const SizedBox(
          height: 40,
          child: Center(
            child: Text("上拉显示更多"),
          ),
        );
      } else if(loadMoreStatus == LoadMoreStatus.loading) {
        return const SizedBox(
          height: 40,
          child: Center(
            child: Text("正在加载..."),
          ),
        );
      } else if(loadMoreStatus == LoadMoreStatus.noMore) {
        return const SizedBox(
          height: 40,
          child: Center(
            child: Text("没有更多数据了"),
          ),
        );
      } else {
        return const SizedBox(
          height: 40,
          child: Center(
            child: Text("出错了-_-,上拉重新加载"),
          ),
        );
      }
    } else {
      ...
    }
  },
  itemCount: datas.length + 1
)

实现上拉加载更多

void _loadMoreData() {
  if(loadMoreStatus == LoadMoreStatus.noMore) {
    return;
  }

  if(loadMoreStatus == LoadMoreStatus.loading) {
    return;
  }

  int page = start;
  if(loadMoreStatus != LoadMoreStatus.error) {
    page += 1;
  }

  setState(() {
    loadMoreStatus = LoadMoreStatus.loading;
  });

  IndexDao.getProjectList(cid: 0, start: page).then((value) {
    start = page;

    setState(() {
      if(value.hasNextPage) {
        loadMoreStatus = LoadMoreStatus.more;
      } else {
        loadMoreStatus = LoadMoreStatus.noMore;
      }
      datas.addAll(value.records);
    });
  }).onError((error, stackTrace) {
    setState(() {
      loadMoreStatus = LoadMoreStatus.error;
    });
    return Future.error(error!, stackTrace);
  });
}

_controller.addListener(() {
  if(_controller.position.extentAfter == 0) {
    //划动到底部了,加载更多数据
    _loadMoreData();
  }
});

在这里插入图片描述

六.Fixed:滑动到最后一页,下拉刷新数据,没有将加载状态重置为more

在这里插入图片描述

Future<void> _refreshData() {
  start = 1;
  setState(() {
    loadMoreStatus = LoadMoreStatus.more;
  });
  
  return IndexDao.getProjectList(cid: 0, start: start).then((value) {

    setState(() {
      datas.clear();
      datas.addAll(value.records);
      hasMore = value?.hasNextPage ?? false;
      if(hasMore) {
        loadMoreStatus = LoadMoreStatus.more;
      } else {
        loadMoreStatus = LoadMoreStatus.noMore;
      }
    });
  });
}

七.Fixed:第一页数据不足一屏时,不能触发下拉刷新和加载更多

这种情况属于极端情况,可根据实际情况考虑是否需要修复,可以使用CustomScrollView结合SliverList、SliverFillRemaining修复

Widget build(BuildContext context) {
  return RefreshIndicator(
    onRefresh: () {
      return _refreshData();
    },
    child: CustomScrollView(
      controller: _controller,
      slivers: [
        SliverPadding(
          padding: EdgeInsets.all(10),
          sliver: SliverList.separated(
            itemCount: datas.length,
            itemBuilder: (BuildContext context, int index) {
              return Container(
                padding: const EdgeInsets.all(10),
                decoration: const BoxDecoration(
                  borderRadius: BorderRadius.all(Radius.circular(5)),
                  color: Colors.white,
                ),
                child: IntrinsicHeight(
                  child: Row(
                    mainAxisSize: MainAxisSize.max,
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    children: [
                      SizedBox(
                        width: 120,
                        height: 1,
                        child: Image.network(datas[index].envelopePic ?? "", fit: BoxFit.cover),
                      ),
                      SizedBox(width: 10,),
                      Expanded(
                        flex: 1,
                        child: Column(
                          mainAxisSize: MainAxisSize.min,
                          mainAxisAlignment: MainAxisAlignment.center,
                          crossAxisAlignment: CrossAxisAlignment.stretch,

                          children: [
                            Text(
                              "${datas[index]?.title}",
                              maxLines: 2,
                              style: const TextStyle(
                                overflow: TextOverflow.ellipsis,
                                fontSize: 16
                              ),
                            ),

                            const SizedBox(
                              height: 10,
                            ),

                            Text(
                              "${datas[index]?.desc}",
                              maxLines: 2,
                              style: const TextStyle(
                                overflow: TextOverflow.ellipsis,
                                fontSize: 14
                              )
                            ),
                          ],
                        )
                      )
                    ],
                  ),
                ),
              );
            },
            separatorBuilder: (BuildContext context, int index) {
              return const Divider(color: Colors.transparent, height: 10,);
            },
          ),
        ),

        //填充剩余空间
        SliverFillRemaining(
          hasScrollBody: false,
          fillOverscroll: false,
          child: Container(),
        ),

        SliverToBoxAdapter(
          child: Container(
            padding: const EdgeInsets.only(bottom: 10),
            height: 40,
            child: Center(
              child: Text(tips),
            ),
          ),
        )
      ],
    )
  );
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/972430.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

亲测有效:虚拟机安装gcc,报错Could not retrieve mirrorlist http://mirrorlist.centos.org

&#xff08;网卡配置资料&#xff09; 原因&#xff1a; 网络问题 报错详情&#xff1a; One of the configured repositories failed (未知),and yum doesnt have enough cached data to continue. At this point the onlysafe thing yum can do is fail. There are a few …

【科普向】Jmeter 如何测试接口保姆式教程

现在对测试人员的要求越来越高&#xff0c;不仅仅要做好功能测试&#xff0c;对接口测试的需求也越来越多&#xff01;所以也越来越多的同学问&#xff0c;怎样才能做好接口测试&#xff1f; 要真正的做好接口测试&#xff0c;并且弄懂如何测试接口&#xff0c;需要从如下几个…

Autofac使用(1)

1.Nuget引入程序包 2.得到容器的建造者 3.配置抽象和具体类之间的关系 4.Build一下得到容器实例 5.基于容器来获取对象的实例了 1、基础使用 ContainerBuilder containerBuilder new ContainerBuilder(); containerBuilder.…

LeetCode 热题 100——找到字符串中所有字母异位词(滑动窗口)

题目链接 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 题目解析 该题目的意思简而言之就是说&#xff0c;从s字符串中寻找与p字符串含有相同字符(次数和种类均相同)的子串&#xff0c;并且将他们的首字符下标集合进数组中进行返回。 滑动窗口解…

arm64架构的linux中断分析

文章目录 1. 中断的概念和作用2. Linux中断处理机制2.1 中断请求2.2 中断处理2.3 中断完成2.4.中断触发和处理步骤详解2.4.1 异常向量表的解读 3. GICv3中断控制器3.1 GICv3中断控制器设备树3.2 GICv3中断控制器驱动 4. GIC的下一级中断控制器4.1 设备树4.2 内核对设备树的处理…

数学建模--退火算法求解最值的Python实现

目录 1.算法流程简介 2.算法核心代码 3.算法效果展示 1.算法流程简介 """ 1.设定退火算法的基础参数 2.设定需要优化的函数,求解该函数的最小值/最大值 3.进行退火过程&#xff0c;随机产生退火解并且纠正,直到冷却 4.绘制可视化图片进行了解退火整体过程 &…

Cyber RT学习笔记---2.基础概念汇总

2.基础概念汇总 在前面我们介绍了Cyber RT的基础介绍以及框架方面的知识&#xff0c;我们对Cyber RT是一个什么样的系统和框架有了一个大概的认知。这节我们将介绍一下Cyber RT中的一些基础且关键的概念&#xff0c;搞清楚这些概念所代表的意义以及作用&#xff0c;之后我们再…

2023年9月DAMA-CDGA/CDGP数据治理认证火热招生中

DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业发展里程碑及发展阶梯定义&#xff0c;帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力&#xff0c;促进开展工作实践应用及实际问题解决&#xff0c;形成企业所需的新数字经济下的核心职业…

Android列表片段

下面创建第二个片段WorkoutFragment&#xff0c;它包含不同训练项目构成的一个列表&#xff0c;用户可以从这个列表中选择训练项目。 列表视图是只包含一个列表的片段 列表片段是一种专门处理列表的片段&#xff0c;它会自动绑定到一个列表视图&#xff0c;所以不需要另外创建…

【 Tkinter界面-练习03】实现动画

一、说明 在本教程中&#xff0c;我们将学习Python Tkinter Animation。在这里&#xff0c;我们将了解如何在 python 中使用 Tkinter 创建动画&#xff0c;并将介绍与动画相关的不同示例。我们还将讨论这些主题 二、Python Tkinter 动画 在本节中&#xff0c;我们将学习 Python…

服务器数据恢复- Ext4文件系统分区挂载报错的数据恢复案例

Ext4文件系统相关概念&#xff1a; 块组&#xff1a;Ext4文件系统的空间被划分为若干个块组&#xff0c;每个块组内的结构大致相同。 块组描述符表&#xff1a;每个块组都对应一个块组描述符&#xff0c;这些块组描述符统一放在文件系统的前部&#xff0c;称为块组描述符表。每…

【python爬虫】15.Scrapy框架实战(热门职位爬取)

文章目录 前言明确目标分析过程企业排行榜的公司信息公司详情页面的招聘信息 代码实现创建项目定义item 创建和编写爬虫文件存储文件修改设置 代码实操总结 前言 上一关&#xff0c;我们学习了Scrapy框架&#xff0c;知道了Scrapy爬虫公司的结构和工作原理。 在Scrapy爬虫公司…

基于IPV6带外BMC在中兴R5500G4服务器上安装ESXi7.0U3n

本文讲解通过IPV6方式从带外BMC在中兴R5500G4服务器上安装VMWare ESXi7.0U3n的过程。 一、安装环境 服务器&#xff1a; 中兴R5500G4服务器(2*16C/384GB MEM/2*480GB SSD/24*8TB SATA/12*960GB SSD/8*10Ge) ISO&#xff1a;VMware-VMvisor-Installer-7.0U3n-21930508.x86_64…

zabbix配置钉钉告警、和故障自愈

钉钉告警python脚本 cat python20 #!/usr/bin/python3 #coding:utf-8 import requests,json,sys,os,datetime # 机器人的Webhook地址 webhook"钉钉" usersys.argv[1] textsys.argv[3] data{"msgtype": "text","text": {"conten…

UniTask保姆级教程

目录 一、UniTask的简介和安装 https://github.com/Cysharp/UniTask.gitpathsrc/UniTask/Assets/Plugins/UniTask 空载性能测试 二、基础用法详解 三、基础用法扩展 四、进阶 五、VContainer简介 六、VContainer基础实例 方便快速查找 一、UniTask的简介和安装 项目地…

如何使用蚂蚁集团自动化混沌工程 ChaosMeta 做 OceanBase 攻防演练?

当前&#xff0c;业界主流的混沌工程项目基本只关注如何制造故障的问题&#xff0c;而经常做演练相关工作的工程师应该明白&#xff0c;每次演练时还会遇到以下痛点&#xff1a; 检测当前环境是否符合演练预设条件&#xff08;演练准入&#xff09;&#xff1b; 业务流量是否满…

第四章网关

文章目录 Gateway服务网关为什么需要网关gateway快速入门断言工厂过滤器工厂路由过滤器的种类默认过滤器全局过滤器自定义全局过滤器 过滤器执行顺序 跨域问题什么是跨域问题 Gateway服务网关 Spring Cloud Gateway 是 Spring Cloud 的一个全新项目&#xff0c;该项目是基于 S…

基于3D数字孪生系统开展水环境治理与修复的功能特点

“小水滴”折射“大民生”&#xff0c;城市供水是极为重要的民生工程、民心工程。上海市委、市政府始终把提高城市供水安全韧性摆在突出位置&#xff0c;着力打造更加完善、更高质量的城市供水网络体系&#xff0c;夯实筑牢城市供水生命线&#xff0c;确保城市水安全。经过多年…

TFTP服务器,NFS服务器

一&#xff0c;安装tftp服务器 1&#xff0c;什么是tftp服务器&#xff1f; tftp服务器是通过网络&#xff0c;将ubuntu程序下载到开发板中 2&#xff0c;安装步骤 1&#xff0c;保证连接外网成功 2&#xff0c;安装tftp服务器 sudo apt-get install tftp-hpa tftpd-hpat…

算法笔记 近似最近邻查找(Approximate Nearest Neighbor Search,ANN)

1 介绍 精准最近邻搜索中数据维度一般较低&#xff0c;所以会采用穷举搜索&#xff0c;即在数据库中依次计算其中样本与所查询数据之间的距离&#xff0c;抽取出所计算出来的距离最小的样本即为所要查找的最近邻。 当数据量非常大的时候&#xff0c;搜索效率急剧下降。——>…