HarmonyOS 6.0 自定义下拉刷新与上拉加载

📅 发布时间:2026/7/27 19:47:43
HarmonyOS 6.0 自定义下拉刷新与上拉加载 列表没有下拉刷新就像网页没有F5——用户刷新不了数据总觉得看到的是旧内容。HarmonyOS的Refresh组件能做基础刷新但真要做出下拉→弹性动画→加载中→收起的完整体验默认配置远远不够。上拉加载更是没有内置支持得自己从头撸。Refresh组件基础最简单的下拉刷新StateisRefreshing:booleanfalseRefresh({refreshing:$$this.isRefreshing}){List(){ForEach(this.items,(item:DataItem){ListItem(){Text(item.title)}},(item:DataItem)item.id)}}.onRefreshing((){this.loadData();})$$this.isRefreshing是双向绑定——Refresh组件会自动设为true触发刷新数据加载完后设为false收起。onRefreshing是刷新触发回调。问题Refresh的默认动画太简陋——就一个圈在转没有弹性下拉效果没有文字提示没有成功/失败反馈。自定义刷新布局用Refresh的builder参数自定义刷新区域StateisRefreshing:booleanfalseStaterefreshState:stringidleBuilderrefreshBuilder(){Row(){if(this.refreshStateidle){Text(下拉刷新).fontSize(14).fontColor(#999999)}elseif(this.refreshStatepulling){Text(释放立即刷新).fontSize(14).fontColor(#666666)}elseif(this.refreshStaterefreshing){LoadingProgress().width(20).height(20).margin({right:8})Text(正在刷新...).fontSize(14).fontColor(#1a73e8)}elseif(this.refreshStatesuccess){Text(刷新成功).fontSize(14).fontColor(#43a047)}elseif(this.refreshStatefailed){Text(刷新失败请重试).fontSize(14).fontColor(#e53935)}}.width(100%).height(60).justifyContent(FlexAlign.Center).alignItems(VerticalAlign.Center)}Refresh({refreshing:$$this.isRefreshing,builder:this.refreshBuilder()}){List(){// 列表内容}}.onRefreshing(async(){this.refreshStaterefreshing;try{awaitthis.loadData();this.refreshStatesuccess;}catch(e){this.refreshStatefailed;}setTimeout((){this.isRefreshingfalse;this.refreshStateidle;},500);})五个状态idle→pulling→refreshing→success/failed→idle。成功和失败显示500ms后收起。下拉距离监听Refresh组件的onPullingDown回调提供下拉距离Refresh({refreshing:$$this.isRefreshing}){List(){// 内容}}.onPullingDown((progress:number){if(progress0.6this.refreshState!refreshing){this.refreshStateidle;}elseif(progress0.6this.refreshState!refreshing){this.refreshStatepulling;}})progress是0~1的进度值0表示未下拉1表示到达触发阈值。0.6是个经验值——下拉超过60%时提示释放刷新。自定义旋转箭头下拉时箭头随进度旋转StatepullProgress:number0BuilderrefreshBuilder(){Row(){Image($r(sys.media.ohos_ic_public_arrow_down)).width(20).height(20).rotate({angle:this.pullProgress*180}).animation({duration:100}).margin({right:8})if(this.refreshStaterefreshing){LoadingProgress().width(20).height(20).margin({right:8})Text(加载中...)}elseif(this.pullProgress0.6){Text(释放刷新)}else{Text(下拉刷新)}}.fontSize(14).fontColor(#666666).width(100%).height(60).justifyContent(FlexAlign.Center)}.onPullingDown((progress:number){this.pullProgressprogress;// 状态判断})pullProgress从0到1箭头旋转0°到180°。超过阈值时箭头朝上提示释放刷新。上拉加载更多HarmonyOS没有内置上拉加载需要自己实现。最简单的方式是在List底部加一个加载触发器StateisLoadingMore:booleanfalseStatehasMore:booleantrueprivatecurrentPage:number1List(){ForEach(this.items,(item:DataItem){ListItem(){// 列表项内容}},(item:DataItem)item.id)// 底部加载指示器if(this.hasMore){ListItem(){Row(){LoadingProgress().width(20).height(20).margin({right:8})Text(加载更多...).fontSize(14).fontColor(#999999)}.width(100%).height(50).justifyContent(FlexAlign.Center)}}else{ListItem(){Text(没有更多了).fontSize(14).fontColor(#cccccc).width(100%).textAlign(TextAlign.Center).padding(16)}}}.onReachEnd((){if(!this.isLoadingMorethis.hasMore){this.loadMore();}})onReachEnd在列表滚动到底部时触发。isLoadingMore防止重复触发hasMore判断是否还有更多数据。loadMore的实现privateasyncloadMore():Promisevoid{this.isLoadingMoretrue;this.currentPage;try{letnewItemsawaitthis.fetchData(this.currentPage);if(newItems.length0){this.hasMorefalse;}else{this.itemsthis.items.concat(newItems);}}catch(e){this.currentPage--;}this.isLoadingMorefalse;}concat合并新旧数据。新数据为空说明到底了设hasMorefalse。手动触发加载更多onReachEnd在快速滚动时可能不触发。可以用onScrollIndex作为补充.onScrollIndex((start:number,end:number){if(endthis.items.length-3!this.isLoadingMorethis.hasMore){this.loadMore();}})距离底部3条数据时提前加载实现预加载效果。用户看到最后几条时新数据已经准备好了。空状态处理首次加载和刷新后都可能有空结果if(this.items.length0){Column(){if(this.isFirstLoad){LoadingProgress().width(40).height(40).margin({bottom:16})Text(加载中...).fontSize(14).fontColor(#999999)}else{Image($r(app.media.empty_state)).width(120).height(120).margin({bottom:16})Text(暂无数据).fontSize(16).fontColor(#999999).margin({bottom:8})Text(点击重试).fontSize(14).fontColor(#1a73e8).onClick((){this.isRefreshingtrue;this.loadData();})}}.width(100%).height(100%).justifyContent(FlexAlign.Center)}else{Refresh({refreshing:$$this.isRefreshing}){List(){// 列表内容}}.onRefreshing((){this.loadData();})}首次加载显示loading加载完成无数据显示空状态重试按钮。有数据时才显示列表。刷新与加载的协调下拉刷新应该重置分页状态privateasyncloadData():Promisevoid{this.currentPage1;this.hasMoretrue;try{this.itemsawaitthis.fetchData(1);this.refreshStatesuccess;}catch(e){this.refreshStatefailed;}setTimeout((){this.isRefreshingfalse;this.refreshStateidle;},500);}刷新时currentPage重置为1hasMore恢复trueitems用新数据替换不是concat。加载更多时才concat追加。关键刷新和加载更多不能同时进行。在isRefreshing为true时禁止触发loadMore在isLoadingMore为true时不响应onRefreshing。列表滚动位置记忆刷新后列表会跳回顶部。要保持位置privatescroller:ScrollernewScroller();privatescrollOffset:number0;List(){// 内容}.scroller(this.scroller).onScroll((){this.scrollOffsetthis.scroller.currentOffset().yOffset;})// 刷新完成后恢复位置asyncloadData():Promisevoid{letsavedOffsetthis.scrollOffset;this.currentPage1;this.itemsawaitthis.fetchData(1);this.isRefreshingfalse;// 恢复滚动位置setTimeout((){this.scroller.scrollTo({xOffset:0,yOffset:savedOffset});},100);}刷新前记录currentOffset刷新后scrollTo恢复。setTimeout 100ms等UI更新完成再滚动。但大多数应用刷新后跳回顶部是预期行为——用户下拉刷新就是想看最新内容。只有静默刷新后台刷新不跳位置才需要记忆位置。踩坑清单问题原因解决刷新动画收不起来isRefreshing没设回falseloadData完成后设false下拉没有弹性没用Refresh组件用Refresh包裹List加载更多重复触发没用isLoadingMore防重加布尔锁没有更多数据还触发hasMore没判断加hasMore标记刷新时触发加载更多两个状态没互斥isRefreshing时禁止loadMoreonReachEnd不触发LazyForEach缓存不够用onScrollIndex提前触发空状态没显示只判断items空没判断加载状态区分首次加载和加载完成刷新后列表跳顶这是默认行为如需保持用scroller记忆位置自定义builder不显示builder参数写法错builder: this.xxxBuilder()下拉进度不准确onPullingDown的progress是0~1progress0.6为可触发下拉刷新用Refresh自定义builder上拉加载用onReachEnd分页控制。两者共享数据源但状态独立——刷新重置分页加载追加数据。记住这个原则任何列表的刷新加载都能搞定。