HarmonyOS NEXT开发实战:实现高效下拉刷新与上拉加载组件(二)刷新核心逻辑与空页面集成

news/2024/12/27 10:30:10/文章来源:https://www.cnblogs.com/wangerdan115/p/18634958

前言:

在上一篇文章中,我们深入探讨了如何在HarmonyOS中实现一个功能完备的空页面组件。现在,我们将进入下拉刷新和上拉加载功能的核心逻辑实现。这不仅仅是技术实现,更是对用户体验的深刻理解。本文将详细介绍如何将空页面与下拉刷新、上拉加载逻辑相结合,打造一个既高效又用户友好的交互体验。

一、核心逻辑的构建

在开发下拉刷新和上拉加载功能时,我们首先需要定义几个关键字段:页面总数、开始页数、页面数据条数。这些字段是分页请求的基础,也是我们实现逻辑的起点。

那么在我们的核心工具类里需要对网络请求做成一个函数参数,进行外部请求
代码示例:

private requestData:(currentPage:number,pageSize:number)=>void

接下来,我们需要实现外部调用监听,以便我们的加载逻辑能够与外部进行沟通。这包括刷新完成、加载完成、数据为空监听等。

代码示例:

export interface PullRefreshListener<T> {refreshCompleted:()=>void; loadMoreCompleted:()=>void;emptyPage:()=>void;setData:(data:T[], isRefreshLast:boolean)=>void;lastData:()=>void;moreLoadFail:(error:BaseError)=>void;onLoadFail:(error:BaseError)=>void;
}

二、下拉刷新与上拉加载的实现

在实现下拉刷新和上拉加载时,我们需要考虑多种状态,包括数据为空、加载错误等。核心逻辑包括判断数据是否为空,是否到达最后一页,以及如何处理加载错误。

核心逻辑代码:

import { BaseError } from '@kangraoo/baselibrary/src/main/ets/exception/NetworkError';
import { Log } from '@kangraoo/utils';export interface PullRefreshListener<T> {refreshCompleted:()=>void;loadMoreCompleted:()=>void;emptyPage:()=>void;setData:(data:T[], isRefreshLast:boolean)=>void;lastData:()=>void;moreLoadFail:(error:BaseError)=>void;onLoadFail:(error:BaseError)=>void;
}export class PullRefreshList<T>{//页面总数readonly PAGE_COUNT_SIZE:number = 10//当前第几页readonly CURRENT_PAGE:number = 1private isRefreshLast:boolean = true//开始private currentPage:number//页数private pageSize: number//网络请求数据啥的private requestData:(currentPage:number,pageSize:number)=>voidprivate pullRefreshListener:PullRefreshListener<T>;constructor(requestData: (currentPage: number, pageSize: number) => void, pullRefreshListener: PullRefreshListener<T>,currentPage?: number, pageSize?: number) {this.currentPage = currentPage??this.CURRENT_PAGE;this.pageSize = pageSize??this.PAGE_COUNT_SIZE;this.requestData = requestData;this.pullRefreshListener = pullRefreshListener;}private makeCurrentPage(){this.currentPage++;Log.debug(`当前 ${this.currentPage}`);this.isRefreshLast = false;}//刷新refreshData() {this.isRefreshLast = true;this.currentPage = 1;this.requestData(this.currentPage,this.pageSize);}///刷新已经加载的数据(是数据存在的情况才可以刷新)refreshLoadData(){this.isRefreshLast = true;Log.debug(`当前${this.currentPage} 总共 ${this.pageSize}*${this.currentPage}`);this.requestData(1,this.pageSize*(this.currentPage--));}///加载loadMore() {this.isRefreshLast = false;this.requestData(this.currentPage,this.pageSize);}dataError(error:BaseError) {this.pullRefreshListener.loadMoreCompleted();this.pullRefreshListener.refreshCompleted();if (this.isRefreshLast) {this.pullRefreshListener.onLoadFail(error);} else {this.pullRefreshListener.moreLoadFail(error);}}dataSucces(data:T[]|null, total:number) {this.pullRefreshListener.loadMoreCompleted();this.pullRefreshListener.refreshCompleted();if (total === 0) {if (this.isRefreshLast) {this.pullRefreshListener.setData([], this.isRefreshLast);this.pullRefreshListener.emptyPage();}} else {if (data === null || data.length===0) {if (this.isRefreshLast) {this.pullRefreshListener.setData([], this.isRefreshLast);this.pullRefreshListener.emptyPage();}} else {Log.debug(`page${this.currentPage},total${total}`);this.pullRefreshListener.setData(data, this.isRefreshLast);if (this.pageSize * this.currentPage >= total) {this.pullRefreshListener.lastData();}this.makeCurrentPage();}}}}

三、控件选择与基础逻辑

选择合适的控件对于实现下拉刷新和上拉加载至关重要。我们选择了系统控件Refresh,它提供了天然的下拉刷新处理和页面定制化功能。

为此我们需要做上篇文章的空页面与refresh控件的结合

首先需要熟悉几个变量

空页面状态 layoutType
上拉刷新结束 finished
上拉加载中 loading
下拉刷新 isRefreshing
刷新状态 refreshStatus

其次我们熟悉几个方法
下拉刷新包裹的内容,一般是list 或者 其他列表 content
下拉调用的方法 onRefreshing
点击空页面上的刷新按钮 onButtonRefreshing

核心代码:

@Preview
@Component
export struct PullRefreshWidget {public mCmpController: PullRefreshController|null = null;aboutToAppear(): void {if (this.mCmpController!=null) {this.mCmpController.attach(this); //绑定控制器}}@State isRefreshing: boolean = false@StaterefreshStatus: RefreshStatus = RefreshStatus.Inactive@Link finished: boolean@Link loading: boolean@Link moreLoadFail: boolean@BuilderParamcontent:()=>voidonRefreshing?:()=>voidonButtonRefreshing?:()=>void@BuilderbaseRefresh(){Refresh({refreshing : $$this.isRefreshing,builder: this.customRefreshComponent()}){this.content()}.onRefreshing(()=>{if(this.onRefreshing){this.onRefreshing()}}).onStateChange(async (status) => {this.refreshStatus = status}).height("100%")}@StatelayoutType : EmptyStatus =  EmptyStatus.nonebuild() {EmptyWidget({child : ()=>{this.baseRefresh()},layoutType : this.layoutType,refresh : ()=>{if(this.onButtonRefreshing){this.onButtonRefreshing()}}})}@BuildercustomRefreshComponent(){Stack(){Row(){LoadingProgress().height(32)Text(this.getTextByStatus()).fontSize(16).margin({left:20})}.alignItems(VerticalAlign.Center)}.align(Alignment.Center).clip(true).constraintSize({minHeight:32}) // 设置最小高度约束保证自定义组件高度随刷新区域高度变化时自定义组件高度不会低于minHeight.width("100%")}getTextByStatus() {switch (this.refreshStatus) {case RefreshStatus.Drag:return Application.getInstance().resourceManager.getStringSync($r("app.string.continue_pull_down").id)case RefreshStatus.OverDrag:return Application.getInstance().resourceManager.getStringSync($r("app.string.release_to_load").id)case RefreshStatus.Refresh:return Application.getInstance().resourceManager.getStringSync($r("app.string.loading").id)}return ""}}export class PullRefreshController{private mComponent: PullRefreshWidget | null = null;attach(component: PullRefreshWidget) {this.mComponent = component;}refreshCompleted(){if(this.mComponent!=null){this.mComponent.isRefreshing = false;}}loadMoreCompleted() {if(this.mComponent!=null){this.mComponent.finished = falsethis.mComponent.moreLoadFail = falsethis.mComponent.loading = false}}lastData(){if(this.mComponent!=null){this.mComponent.finished = true}}moreLoadFail(){if(this.mComponent!=null){this.mComponent.moreLoadFail = true;}}emptyPage(){if(this.mComponent!=null){this.mComponent.layoutType = EmptyStatus.nodata}}nonePage(){if(this.mComponent!=null){this.mComponent.layoutType = EmptyStatus.none}}onLoadFail(){if(this.mComponent!=null){this.mComponent.layoutType = EmptyStatus.fail}}}

四、数据源的选择与实现

在业务上,为了使用瀑布流,我采用了WaterFlow作为数据组件。同时,我们实现了一个数据源类,以支持数据的动态加载和更新。

首先由于我采用LazyForEach 关于数据源需要做一个 封装类

BasicDataSource 是实现 IDataSource来写一些方法

// Basic implementation of IDataSource to handle data listener
export class BasicDataSource<T> implements IDataSource {private listeners: DataChangeListener[] = [];private originDataArray: T[] = [];public totalCount(): number {return this.originDataArray.length;}public getData(index: number): T {return this.originDataArray[index];}// 注册改变数据的控制器registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {this.listeners.push(listener)}}// 注销改变数据的控制器unregisterDataChangeListener(listener: DataChangeListener): void {const pos = this.listeners.indexOf(listener)if (pos >= 0) {this.listeners.splice(pos, 1)}}// 通知控制器数据重新加载notifyDataReload(): void {this.listeners.forEach(listener => {listener.onDataReloaded()})}// 通知控制器数据增加notifyDataAdd(index: number): void {this.listeners.forEach(listener => {listener.onDataAdd(index)})}// 通知控制器数据变化notifyDataChange(index: number): void {this.listeners.forEach(listener => {listener.onDataChange(index)})}// 通知控制器数据删除notifyDataDelete(index: number): void {this.listeners.forEach(listener => {listener.onDataDelete(index)})}// 通知控制器数据位置变化notifyDataMove(from: number, to: number): void {this.listeners.forEach(listener => {listener.onDataMove(from, to)})}// 指定位置添加一个数据public addData(index: number, data: T): void {this.originDataArray.splice(index, 0, data);this.notifyDataAdd(index);}// 在第一个位置增加数据public add1stItem(data: T): void {this.addData(0,data)}// 添加一个数据public pushData(data: T): void {this.originDataArray.push(data);this.notifyDataAdd(this.originDataArray.length - 1);}// 在指定索引位置删除一个元素public deleteItem(index: number): void {this.originDataArray.splice(index, 1)this.notifyDataDelete(index)}// 删除第一个元素public delete1stItem(): void {this.deleteItem(0)}// 删除最后一个元素public deleteLastItem(): void {this.originDataArray.splice(-1, 1)this.notifyDataDelete(this.originDataArray.length)}// 清除数据public clearData () {this.originDataArray = []this.notifyDataReload()}// 设置新数据public setData(dataArray: T[]){this.originDataArray = dataArraythis.notifyDataReload()}//添加list数据public addDatas(dataArray: T[]){let l = this.originDataArray.lengththis.originDataArray.push(...dataArray)this.notifyDataAdd(l-1)// this.originDataArray.push(...dataArray)// this.notifyDataReload()}}

我的数据类是ExperienceListResponse,我需要实现这个数据源

class WaterFlowDataSource extends BasicDataSource<ExperienceListResponse> {}

五、完整的下拉刷新实现

最后,我们将所有组件和逻辑整合在一起,实现一个完整的下拉刷新功能。这包括数据的加载、状态的更新以及用户交互的处理。

完整代码:


@Component
export struct MyPullRefreshWidget{@State list:ExperienceListResponse[] = []dataSource: WaterFlowDataSource = new WaterFlowDataSource()mCmpController: PullRefreshController = new PullRefreshController()pullRefreshList :PullRefreshList<ExperienceListResponse> = new PullRefreshList((currentPage,pageSize)=>{setTimeout(()=>{QuickResponsitory.getInstance().experienceList(currentPage,pageSize).then(value=>{LibLoading.hide();if (value instanceof SuccessData) {let data = value as (SuccessData<ApiResult<ExperienceListResponse[]>>)this.list = data.data?.data ?? []this.pullRefreshList.dataSucces(this.list,data.data?.page?.totalCount??0)} else if (value instanceof ErrorData) {this.pullRefreshList.dataError(value.error)}})},1000)},{refreshCompleted:()=>{this.mCmpController.refreshCompleted()},loadMoreCompleted:()=> {this.mCmpController.loadMoreCompleted()},emptyPage:()=> {this.mCmpController.emptyPage()},setData:(data:ExperienceListResponse[], isRefreshLast:boolean)=>{if(isRefreshLast){this.mCmpController.nonePage()this.dataSource.setData(data)}else{this.dataSource.addDatas(data)}},lastData:()=> {this.mCmpController.lastData()},moreLoadFail:(error:BaseError)=>{this.mCmpController.moreLoadFail()},onLoadFail:(error:BaseError)=>{this.mCmpController.onLoadFail()}})aboutToAppear(): void {LibLoading.show();this.pullRefreshList.refreshData()}@State finished: boolean = false // 是否已经加载完成@State loading: boolean = false@State moreLoadFail: boolean = false@BuilderitemFoot() {if (this.finished) {Row() {Text($r("app.string.no_more_data")).fontSize(12)}.width("100%").height(40).justifyContent(FlexAlign.Center)} else {if (this.loading) {// 正在加载中Row({ space: 10 }) {Text($r("app.string.loading_data")).fontSize(12)LoadingProgress().width(20).height(20)}.width("100%").height(40).justifyContent(FlexAlign.Center)}else {if(this.moreLoadFail){Row() {Text($r("app.string.data_loading_failed")).fontSize(12)}.width("100%").height(40).justifyContent(FlexAlign.Center)}}}}@BuilderdataContent(){WaterFlow({footer:this.itemFoot()}){LazyForEach(this.dataSource,(item:ExperienceListResponse,index:number)=>{FlowItem(){ExperienceListItem({experience:item}).padding(4)}},(item:ExperienceListResponse,index:number)=>{return item.id})}.layoutDirection(FlexDirection.Column).columnsTemplate("1fr 1fr").onReachEnd(()=>{// 阀门控制if (!this.loading && !this.finished) {this.loading = truethis.pullRefreshList.loadMore()}})}build() {PullRefreshWidget({mCmpController:this.mCmpController,content:()=>{this.dataContent()},onRefreshing:()=>{this.pullRefreshList.refreshData()},onButtonRefreshing:()=>{LibLoading.show();this.pullRefreshList.refreshData()},finished:this.finished,loading:this.loading,moreLoadFail:this.moreLoadFail})}
}

五、深入解析与经验分享

在实现下拉刷新与上拉加载的过程中,我遇到了一些挑战,比如如何确保数据加载的流畅性,如何处理网络请求的异常,以及如何与空页面进行有效的集成。通过不断的测试和优化,我们找到了一些解决方案,使得整个组件不仅功能强大,而且用户体验良好。

总结:

通过本文,我们不仅学习了如何在HarmonyOS中实现下拉刷新和上拉加载的核心逻辑,还了解了如何将这些逻辑与空页面组件相结合,以提供更加丰富和流畅的用户体验。

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

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

相关文章

鸿蒙OS创新实践:动态声控话筒开发指南

前言 在鸿蒙OS的生态中,开发者们不断探索和创新,以期为用户带来更丰富的交互体验。最近,我萌生了一个想法:制作一个能够随着声音动态变化的话筒组件。尽管网络上缺乏现成的参考案例,但我决定亲自动手,将这一创意变为现实。本文将深入解析这一开发过程,分享我的实战经验和…

华为云电脑怎么搭建平台,云电脑搭建的设置方法

在全球化的今天,远程连接已经成为了企业和个人不可或缺的一部分。它不仅能够帮助企业实现全球化的业务布局,拓展市场空间,还能够为个人提供更多的发展机会和自由,让我们能够更好地适应快速变化的社会环境。这次给大家介绍云电脑搭建的设置方法?云电脑搭建的设置方法? 设置…

如何解决WDCP控制面板无法登录的问题?

您好,关于您提到的WDCP控制面板无法登录的问题,以下是详细的排查和解决方案:检查服务器状态:首先,确认服务器是否处于正常运行状态。使用SSH或远程桌面工具登录到服务器,查看服务器的启动日志和系统状态。 如果服务器处于只读模式,可能是由于磁盘故障或其他系统问题导致…

《DNK210使用指南 -CanMV版 V1.0》第四十七章 MNIST实验

第四十七章 MNIST实验 1)实验平台:正点原子DNK210开发板 2)章节摘自【正点原子】DNK210使用指南 - CanMV版 V1.0 3)购买链接:https://detail.tmall.com/item.htm?&id=782801398750 4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/k210/ATK-…

如何高效地创建和管理CMS中的模块与栏目?

在CMS中创建和管理模块及栏目是网站结构规划的重要组成部分,直接影响到用户体验和SEO表现。为了实现高效的管理和组织,以下是几个实用的建议: 一、清晰定义网站架构 在开始创建模块和栏目之前,首先要对网站的整体架构有一个清晰的认识。确定好每个页面的功能定位,比如首页…

安装CMS程序需要注意哪些事项?

安装CMS程序是将下载好的内容管理系统部署到服务器上的关键步骤。为了确保安装过程顺利无误,以下几点是你应该特别注意的: 首先,确保服务器环境符合CMS的要求。不同的CMS对服务器有不同的要求,比如PHP版本、MySQL数据库版本等。通常这些信息可以在CMS的官方文档中找到。如果…

如何处理云服务器IP变更及后续调整?

您好,关于您提到的云服务器IP变更问题,以下是详细的处理步骤和注意事项:确认新IP地址:首先,我们需要确认新的IP地址。根据您的描述,新IP为127.0.0.1(实际情况下,这通常是一个本地回环地址,用于测试或内部通信,而非公网IP)。请确保您收到的是正确的公网IP地址,并在需…

如何在Windows上正确启用PHP的mbstring扩展?

1. 确保 php_mbstring.dll 文件存在 首先,你需要确认你的PHP安装目录中确实包含了php_mbstring.dll文件。通常情况下,这个文件位于PHP安装目录下的ext文件夹中。如果你没有找到这个文件,可能是因为你下载的PHP版本默认没有包含这个扩展。此时,你可以考虑重新下载一个完整的…

六边形图片展示

六边形图片展示 html代码展示<div class="boxF"><div class="boxS"><div class="boxT" :style="{background:url(+ $global.picCodePolice(item.faceImg) +)}"></div></div> </div>相关的css展示…

网站被挂木马,如何紧急处理并加强防护?

您好!当您的网站被挂木马时,需要立即采取一系列措施来清理木马文件,并防止未来的攻击。以下是详细的处理步骤和建议:立即停止传播有害信息:根据国家相关法律规定,网站主办者有责任确保网站不传播有害信息。一旦发现有害信息,必须在24小时内清除所有有害内容,并采取必要…

【GreatSQL优化器-08】statistics和index dives

【GreatSQL优化器-08】statistics和index dives 一、statistics和index_dives介绍 GreatSQL的优化器对于查询条件带有范围的情况,需要根据 mm tree 来估计该范围内大概有多少行,然后以此来计算cost。对于等号条件,给出了两种方法来估计对应行数--Statistics和index dives,前…