HarmonyOS Next 系列之列表下拉刷新和触底加载更多数据实现(十一)

系列文章目录

HarmonyOS Next 系列之省市区弹窗选择器实现(一)
HarmonyOS Next 系列之验证码输入组件实现(二)
HarmonyOS Next 系列之底部标签栏TabBar实现(三)
HarmonyOS Next 系列之HTTP请求封装和Token持久化存储(四)
HarmonyOS Next 系列之从手机选择图片或拍照上传功能实现(五)
HarmonyOS Next 系列之可移动悬浮按钮实现(六)
HarmonyOS Next 系列之沉浸式状态实现的多种方式(七)
HarmonyOS Next系列之Echarts图表组件(折线图、柱状图、饼图等)实现(八)
HarmonyOS Next系列之地图组件(Map Kit)使用(九)
HarmonyOS Next系列之半圆环进度条实现(十)
HarmonyOS Next 系列之列表下拉刷新和触底加载更多数据实现(十一)


文章目录

  • 系列文章目录
  • 前言
  • 一、下拉刷新
    • 1、实现解析
    • 2、Refresh简单回顾
    • 3、代码实现
      • (1) 默认样式
      • (2) 自定义样式
  • 二、上拉触底加载更多
    • 1、实现解析
    • 2、触底交互布局设计
    • 3、List和Scroll区别
      • 总结
    • 4、代码实现
      • LoadingMoreView.ets
      • ListPage.ets


前言

HarmonyOS Next(基于API12)实现下拉刷新和上拉触底加载更多功能。 下拉刷新和触底加载作为实战项目列表页中最常见的2种操作,本文将通过示例讲解这2种功能实现,以及需要注意的埋坑点。

下拉刷新示例:
请添加图片描述

触底加载示例:
请添加图片描述


一、下拉刷新

1、实现解析

官方已经提供了Refresh(下拉刷新组件),只要通过监听下拉状态,根据状态变化改变界面样式或文字提示并在
下拉处于加载状态下去请求接口刷新数据即可。组件默认样式是个简易版本,也可以通过传入builder自定义更加炫酷的界面。

2、Refresh简单回顾

入参:

只有一个value: RefreshOptions

RefreshOptions对象类型常用属性字段:

refreshing:是否显示下拉刷新组件,支持$$双向绑定,boolean类型
builder:自定义样式布局,CustomBuilder类型
promptText:设置刷新区域底部显示的自定义文本,设置了builder此字段无效,ResourceStr类型

常用属性:

refreshOffset:设置触发刷新的下拉偏移量,下拉超过该值触发刷新状态,类型number,单位vp

常用事件:

onStateChange:当前刷新状态变更时,触发回调,onStateChange(callback: (state: RefreshStatus) => void)

状态RefreshStatus枚举值:

名称描述
Inactive0默认未下拉状态。
Drag1下拉中,下拉距离小于刷新距离。
OverDrag2下拉中,下拉距离超过刷新距离。
Refresh3下拉结束,回弹至刷新距离,进入刷新状态。
Done4刷新结束,返回初始状态(顶部)。

其中我们主要关心Drag、OverDrag、Refresh三种状态即可

3、代码实现

(1) 默认样式

PullRefresh.ets:

/*** 下拉刷新-默认样式*/
@Entry
@Component
struct PullRefresh {@State isRefreshing: boolean = false //是否正在刷新@State promptText: string = '' //下拉提示文字@State list: number[] = [] //列表数据aboutToAppear(): void {this.getList()}getList() {//模拟接口获取列表数据setTimeout(() => {for (let i = 0; i < 20; i++) {this.list.push(i)}this.isRefreshing = false}, 1000)}build() {Refresh({ refreshing: $$this.isRefreshing, promptText: this.promptText }) {List({ space: 20 }) {ForEach(this.list, (item: number) => {ListItem() {Text(item.toString())}.width('100%').height(90).backgroundColor('#fff').borderRadius(10)}, (item: number) => item.toString())}.height('100%').width('100%').padding(20).backgroundColor('#f2f2f2')}.onStateChange(async (state) => {switch (state) {//下拉中,下拉距离小于刷新距离case RefreshStatus.Drag:this.promptText = '下拉可以刷新'break;//下拉中,下拉距离超过刷新距离case RefreshStatus.OverDrag:this.promptText = '释放立即刷新'break;//刷新状态case RefreshStatus.Refresh:this.promptText = "正在刷新..."this.getList()break;}})}
}

运行效果:
请添加图片描述

(2) 自定义样式

样式设计:
(1)下拉距离小于刷新距离左边图标显示向下箭头,右边文字显示下拉可以刷新
(2)下拉距离超过刷新距离左边图标显示向上箭头,右边文字释放立即刷新
(3)刷新状态左边图标显示加载转圈动画,右边文字显示正在刷新…
所有状态右边底部都显示上次更新时间

PullRefresh.ets:

/*** 下拉刷新-自定义样式*/
@Entry
@Component
struct PullRefresh{@State isRefreshing: boolean = false //是否正在刷新@State promptText: string = '' //下拉提示文字@State refreshStatus:number=0 // 刷新状态@State list:number[]=[]//列表数据@State lastTime:string=this.getDateTime()//上次刷新时间aboutToAppear(): void {this.getList()}//获取列表数据getList(){//模拟接口获取列表数据setTimeout(()=>{for(let i=0;i<20;i++){this.list.push(i)}//关闭下拉刷新this.isRefreshing=false},1000)}//获取当前日期时间MM-dd HH:ssgetDateTime(){let date: Date = new Date()let month = (date.getMonth() + 1).toString().padStart(2, '0')let day = date.getDate().toString().padStart(2, '0')let hour = date.getHours().toString().padStart(2, '0')let minus = date.getMinutes().toString().padStart(2, '0')return `${month}-${day} ${hour}:${minus}`}//自定义刷新区域内容@Builder customRefreshComponent(){Row({space:15}){//下拉刷新if(this.refreshStatus===RefreshStatus.Drag){Image($r('app.media.arrow_down')).width(20)}//释放刷新else if(this.refreshStatus===RefreshStatus.OverDrag){Image($r('app.media.arrow_up')).width(20)}//刷新中else if(this.refreshStatus===RefreshStatus.Refresh){Image($r('app.media.loading')).width(20).transition(TransitionEffect.rotate({ angle: -360 }).animation({ iterations: -1, curve: Curve.Linear, duration: 2000 }))}Column({space:3}){Text(this.promptText).fontSize(14).fontColor('#666').lineHeight(21)Text(`上次更新 ${this.lastTime}`).fontSize(11).fontColor('#808080')}}.width('100%').constraintSize({minHeight:50}).justifyContent(FlexAlign.Center)}build() {Refresh({ refreshing: $$this.isRefreshing, builder:this.customRefreshComponent() }) {List({space:20}){ForEach(this.list,(item:number)=>{ListItem(){Text(item.toString())}.width('100%').height(90).backgroundColor('#fff').borderRadius(10)},(item:number)=>item.toString())}.height('100%').width('100%').padding(20).backgroundColor('#f2f2f2')}.refreshOffset(100)//触发刷新的下拉偏移量,单位vp//下拉刷新状态监听.onStateChange(async (state) => {this.refreshStatus=stateswitch (state) {//下拉中,下拉距离小于刷新距离case RefreshStatus.Drag:this.promptText = '下拉可以刷新'break;//下拉中,下拉距离超过刷新距离case RefreshStatus.OverDrag:this.promptText = '释放立即刷新'break;//刷新状态case RefreshStatus.Refresh:this.promptText = "正在刷新..."this.getList()break;//刷新结束case RefreshStatus.Done://保存更新时间this.lastTime=this.getDateTime()break;}})}
}

应用到的三张图标:

请添加图片描述
请添加图片描述
请添加图片描述

运行效果:

请添加图片描述

二、上拉触底加载更多

1、实现解析

列表触底加载更多数据,可以选择List组件也可以选择Scroll组件来实现,两个组件都有一个触底回调方法onReachEnd,两者使用上不同请看下文分析。通过触底回调接口请求下一页数据,把新数据追加到原数据上就实现更多数据展示,直到下一页数据为空或者判断当前列表渲染的数据个数已达到总个数,如果是表示已经没有更多数据,之后不再触发加载数据行为。为了更好的交互体验,需要为触底过程绘制不同交互样式比如不同文字提醒,让用户看到触底过程所处状态,最后需要注意触底操作可能由于用户多次操作短时间内高频率触发,需要做节流处理,当上一次数据请求还未完成不能进行下一次触底加载。

2、触底交互布局设计

整个过程可分为3种状态:初始状态:还未进行下一次触底前或进行接口数据请求前状态,文字显示“—— 下拉加载更多 ——”
加载状态:进行接口数据请求时候状态, 图标转圈动画和文字显示"正在加载"
无数据状态:接口请求完后下一页没任何数据时候状态,文字显示"—— 已到底了 ——"

请添加图片描述

在这里插入图片描述
请添加图片描述

3、List和Scroll区别

List示例:


@Entry
@Component
struct Index {@State list: number[] = [] //列表数据aboutToAppear():void {this.init()}//初始化init(){setTimeout(()=>{//模拟10条数据for(let i=0;i<10;i++){this.list.push(i)}},500)}build() {List({ space: 20 }) {ForEach(this.list, (item: number) => {ListItem() {Text(item.toString())}.width('100%').height(90).backgroundColor('#fff').borderRadius(10)}, (item: number) => item.toString())}.width('100%').padding(20).backgroundColor('#f2f2f2').onReachEnd(()=>{console.log('触底')})}}

运行:
在这里插入图片描述
发现在未进行任何操作情况下,首次渲染会触发一次触底事件

Scroll示例:

@Entry
@Component
struct Index {@State list: number[] = [] //列表数据aboutToAppear(): void {this.init()}//初始化init() {setTimeout(() => {//模拟10条数据for (let i = 0; i < 10; i++) {this.list.push(i)}}, 500)}build() {Scroll() {List({ space: 20 }) {ForEach(this.list, (item: number) => {ListItem() {Text(item.toString())}.width('100%').height(90).backgroundColor('#fff').borderRadius(10)}, (item: number) => item.toString())}.width('100%').padding(20).backgroundColor('#f2f2f2')}.height('100%').onReachEnd(() => {console.log('触底')})}
}

运行:
在这里插入图片描述
首次渲染不会触发触底事件

总结

List和Scroll触底事件主要区别在于List首次渲染会执行一次触底事件而Scroll不会。另一点两者使用区别上Scroll必须设置高度才有滚动条而List不需要。

避免List首次触发触底解决办法:

可以定义一个boolean变量标识表示是否可以执行触底加载逻辑,默认值false,当列表首次数据请求完成后再把这个标识打开即可避免首次执行,也就相当于延迟打开这个触底开关。

示例:

@Entry
@Component
struct Index {@State list: number[] = [] //列表数据private canLoadingMore: boolean = false //是否可以加载更多aboutToAppear(): void {this.init()}//初始化init() {setTimeout(() => {//模拟10条数据for (let i = 0; i < 10; i++) {this.list.push(i)}//首次请求完数据打开this.canLoadingMore=true}, 500)}build() {List({ space: 20 }) {ForEach(this.list, (item: number) => {ListItem() {Text(item.toString())}.width('100%').height(90).backgroundColor('#fff').borderRadius(10)}, (item: number) => item.toString())}.width('100%').padding(20).backgroundColor('#f2f2f2').onReachEnd(() => {if(this.canLoadingMore){//触底逻辑console.log('触底')}})}
}

4、代码实现

LoadingMoreView.ets

触底显示的交互组件

@Extend(Text)
function textStyle() {.fontSize(13).fontColor('#999').lineHeight(20)
}@Component
export default struct LoadingMoreView {@Link visible: boolean//组件是否显示@Prop status: number = 0 //触底状态 0:初始状态,1:加载状态 2:已到底了build() {Row() {//初始态if (this.status === 0) {Text('—— 下拉加载更多 ——').textStyle()}//加载中else if (this.status === 1) {Row({ space: 5 }) {Image($r('app.media.loading')).width(20).transition(TransitionEffect.rotate({ angle: -360 }).animation({ duration: 2000, curve: Curve.Linear, iterations: -1 }))Text('正在加载').textStyle()}}//已到底else if (this.status === 2) {Text('—— 已到底了 ——').textStyle()}}.width('100%').justifyContent(FlexAlign.Center).padding(5).visibility(this.visible ? Visibility.Visible : Visibility.None)}
}

说明:

组件定义了visible参数,支持双向绑定设置组件是否显示。
定义了status表示不同状态,不同状态分别显示不同布局,三种枚举状态分别为 0:初始状态,1:加载状态 2:已到底了

ListPage.ets

列表页,基于List实现

/*** 触底加载更多*/
import  LoadingMoreView from '../components/LoadingMoreView' //触底组件
@Entry
@Component
struct ListPage {@State list: number[] = [] //列表数据private pageSize: number = 10 //分页每页个数private pageNo: number = 1 //分页当前页数@State  reachStatus:number=0//触底状态 0:初始状态,1:加载状态 2:已到底了private isLoadingMore:boolean=false //是否正在通过接口请求加载数据private  initCompleted:boolean=false //初始化是否完成@State loadingMoreVisible:boolean=false //加载更多组件是否显示aboutToAppear():void {this.init()}//初始化async init(){try {this.list = await this.getList(1)} catch (e) {} finally {this.initCompleted=true}}//接口获取列表数据,入参pageNo:分页页数,返回请求后的数据getList(pageNo: number): Promise<Array<number>> {return new Promise((resolve, reject) => {//模拟接口数据,总共30条数据,每次返回10条setTimeout(() => {if(pageNo<4){let newData:number[]=[]//每次返回10条for(let i=(pageNo-1)*this.pageSize;i<pageNo*this.pageSize;i++){newData.push(i)}resolve(newData)}else {resolve([])}}, 1000)})}//触底加载更多数据async handleLoadingMore(){//防止多次请求,触底请求完才能进行下一次,节流if (this.isLoadingMore) {return}this.isLoadingMore = true//设置组件处于加载状态this.reachStatus=1//请求下一页数据let pageNo = this.pageNo + 1try {let data = await this.getList(pageNo)//有新数据if (data && data.length > 0) {//延迟500毫秒,防止接口响应过快使得肉眼能看到加载转圈动画setTimeout(() => {//新数据追加到listthis.list = [...this.list , ...data]this.pageNo += 1this.isLoadingMore = false//设置组件为初始状态this.reachStatus=0}, 500)}//到底了else {this.isLoadingMore = false//设置组件为到底状态this.reachStatus=2}} catch (e) {this.isLoadingMore = false}}build() {List({ space: 20 }) {ForEach(this.list, (item: number) => {ListItem() {Text(item.toString())}.width('100%').height(90).backgroundColor('#fff').borderRadius(10)}, (item: number) => item.toString())LoadingMoreView({visible:this.loadingMoreVisible, status:this.reachStatus})}.height('100%').width('100%').padding(20).backgroundColor('#f2f2f2').onReachEnd(()=>{//初始化完成由于请求接口有个时间延迟防止首次渲染会执行一次触底if(this.initCompleted&&this.reachStatus!==2){//首次触底后才显示加载组件this.loadingMoreVisible=true//加载数据处理this.handleLoadingMore()}})}}

运行效果:
请添加图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32入门开发操作记录(九)——外部时钟定时器

目录 一、项目准备1. 工程模板2. 器件接线 二、外部时钟1. 端口复用2. 流程示意 三、定时器模块Timer.cTimer.h 四、遮光计数 一、项目准备 1. 工程模板 本篇项目所用模板包含以下模块&#xff0c;声明函数见头文件&#xff0c;模块添加和函数功能详见往期记录。   2. 器件…

Python之格式化输出

格式化输出 方法一&#xff1a;用%方法二&#xff1a;用format()函数设置输出的内容的宽度和小数位数 方法一&#xff1a;用% 直接用print()函数对字符串进行输出&#xff0c;是没有进行格式化控制的。 格式化&#xff0c;是对输出内容的显示方式进行设置。 首先&#xff0c;要…

小程序滑动单元格

项目场景&#xff1a;小程序用户管理列表&#xff0c;通过单元格滑动实现“密码重置”、“删除”功能。 技术框架&#xff1a;uniapp、uview3、ts 效果如下&#xff1a; 前端页面&#xff1a; <template><view class"fui-wrap"><view class"f…

Spring Boot 的Web开发

Spring Boot 的Web开发 一、 静态资源映射规则 总结&#xff1a; 只要静态资源放在类路径下&#xff1a; called /static (or /public or /resources or /METAINF/resources 访问 &#xff1a; 当前项目根路径/ 静态资源名 二、 enjoy模板引擎 Enjoy模板引擎是一个轻量级的…

数据结构-常见的七大排序

上节中我们学习了七大排序中的五种(插入排序、希尔排序、堆排序、选择排序、交换排序) 数据结构-常见的七大排序-CSDN博客 这节我们将要学习快速排序(hoare、指针法、挖洞法(快排的延伸)、快速排序非递归(栈)) 1.快速排序 1.1 hoare法 1.1思路 1.选出一个key&#xff0c;一…

浅看MySQL数据库

有这么一句话&#xff1a;“一个不会数据库的程序员不是合格的程序员”。有点夸张&#xff0c;但是确是如此。透彻学习数据库是要学习好多知识&#xff0c;需要学的东西也是偏难的。我们今天来看数据库MySQL的一些简单基础东西&#xff0c;跟着小编一起来看一下吧。 什么是数据…

Windows 11环境下安装uwsgi的步骤和方法

正在用Django做个小网站&#xff0c;经常要用runserver启动服务观察效果&#xff0c;很不方便&#xff0c;就想装个uwsgi&#xff0c;让服务总是在后台运行&#xff0c;免得切换。上网一查发现&#xff0c;在windows下安装uwsgi不是一件简单的事情&#xff0c;很多人在尝试之后…

Python | Leetcode Python题解之第338题比特位计数

题目&#xff1a; 题解&#xff1a; class Solution:def countBits(self, n: int) -> List[int]:bits [0]for i in range(1, n 1):bits.append(bits[i & (i - 1)] 1)return bits

Spring Web MVC入门(下)

1. 响应 1.1 返回静态页面 创建前端页面&#xff0c;如下图所示&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Index页面</title> </head> <body>Hello,Spring MVC…

设计模式21-组合模式

设计模式21-组合模式&#xff08;Composite Pattern&#xff09; 写在前面 动机定义与结构定义结构主要类及其关系 C代码推导优缺点应用场景总结补充叶子节点不重载这三个方法叶子节点重载这三个方法结论 写在前面 数据结构模式 常常有一些组件在内部具有特定的数据结构。如何…

CVPR2023《DNF: Decouple and Feedback Network for Seeing in the Dark》暗光图像增强论文阅读笔记

相关链接 论文链接 https://openaccess.thecvf.com/content/CVPR2023/papers/Jin_DNF_Decouple_and_Feedback_Network_for_Seeing_in_the_Dark_CVPR_2023_paper.pdf 代码链接 https://github.com/Srameo/DNF 摘要 RAW数据的独特属性在低光照图像增强方面展现出巨大潜力。…

使用RKNN在Orange Pi 5 (RK3588s) 上部署推理PPO深度学习模型

文章目录 一、前言1️⃣、Orange Pi 是什么&#xff1f;2️⃣、PPO 是什么&#xff1f;3️⃣、RKNN 是什么&#xff1f;3️⃣、ONNX 是什么&#xff1f; 二、项目简介三、部署流程1️⃣、PPO 网络结构2️⃣、PPO 输出模型&#xff0c;模型转换&#xff0c;以及对比检查3️⃣、.…

ECMAScript6语法:默认参数和rest参数

1、默认参数 默认参数即在定义函数的参数列表中指定了默认值的参数。在 ES5 中&#xff0c;并没有提供在参数列表中指定参数默认值的语法&#xff0c;要想为函数的参数指定默认值&#xff0c;只能在函数体中实现&#xff0c;示例代码如下&#xff1a; function table(width, …

【性能优化】使用Perfetto定位应用启动性能的瓶颈

Android应用启动优化相关的文章已经有很多人都写过了&#xff0c;但是主要都是聚焦在&#xff0c;为了启动性能都做了哪些改动上&#xff0c;少见有文章会说应该如何分析、识别应用的启动性能。 本篇文章将会结合我个人对Perfetto的实际使用经历&#xff0c;讲解车载应用的启动…

前端post传入拿到数据,后端报null,并且能够添加或者编辑成功

检查conterller层注解接到实体类的注解是不是没加&#xff08; RequestBody &#xff09; 后端&#xff1a; 前端&#xff1a; 那么就看注解&#xff0c;因为contrller层有个接值注解&#xff08; RequestBody &#xff09;

MySQL基础练习题44-只出现一次的最大数字

目录 题目 情况一 准备数据 分析数据 情况二 准备数据 实现一 题目 单一数字 是在 MyNumbers 表中只出现一次的数字。 找出最大的 单一数字 。如果不存在 单一数字 &#xff0c;则返回 null 。 情况一 准备数据 ## 创建库 create database db; use db;## 创建表 Cre…

代码随想录算法训练营Day42||Leetcode300.最长递增子序列 、 674. 最长连续递增序列 、 718. 最长重复子数组

一、最长递增子序列 简单&#xff0c;只不过返回值不是dp数组最后一个元素了&#xff0c;自己做出来AC了 class Solution { public:int lengthOfLIS(vector<int>& nums) {vector<int>dp(nums.size()1,1);for(int i1;i<nums.size();i){for(int ji-1;j>0…

自动化运维---ansible

ansible是一种由Python开发的自动化运维工具&#xff0c;集合了众多运维工具&#xff08;puppet、cfengine、chef、func、fabric&#xff09;的优点&#xff0c;实现了批量系统配置、批量程序部署、批量运行命令等功能。 特点: 部署简单 默认使用ssh进行管理&#xff0c;基于py…

万能钥匙:解锁 C++ 模板的无限可能

1.泛型编程 1.1:交换两个数(C语言) 1.2:交换两个数(C) 1.3:泛型编程 2:函数模板 2.1:函数模板的概念 2.2:函数模板的格式 ​编辑 2.3:函数模板的原理 2.4:模板的实例化 2.4.1:隐式实例化 2.4.2:显式实例化:在函数名后的<>中指定模板参数的实际类型. 2.4.2.1…

Docker 部署 XXL-JOB

Docker 部署 XXL-JOB 目录 引言环境准备创建 MySQL 用户并授予权限使用 Docker 部署 XXL-JOB配置 XXL-JOB验证部署总结 1. 引言 XXL-JOB 是一个开源的分布式任务调度平台&#xff0c;旨在简化定时任务的管理和调度操作。其强大的功能和灵活性&#xff0c;使其在互联网公司和…