HarmonyOS 鸿蒙Next 6 如何实现自定义下拉刷新组件(适配 List/Grid)?
HarmonyOS 鸿蒙Next 6 如何实现自定义下拉刷新组件(适配 List/Grid)?
问题描述
鸿蒙 6 原生List组件的下拉刷新(onRefresh)样式固定,无法满足产品自定义需求(如修改刷新图标、添加加载动画、调整下拉距离)。如何实现通用的自定义下拉刷新组件,同时适配List和Grid组件,且支持鸿蒙 6 的弹性滑动效果?关键字:鸿蒙 6、自定义下拉刷新、List/Grid 适配、弹性滑动、Stage 模型、API20
一、原理解析
自定义下拉刷新的核心是利用鸿蒙的Scroll组件 +AnimatableProperty动画,实现逻辑:
- 外层用
Scroll组件包裹List/Grid,监听onScroll事件获取下拉距离; - 根据下拉距离分状态:默认状态→下拉中→刷新中→刷新完成;
- 通过
animateTo实现刷新图标 / 动画的过渡效果,适配弹性滑动。
二、完整实现代码
1. 自定义下拉刷新组件(RefreshLayout.ets)
import { AnimatableProperty, animateTo } from '@ohos.arkui.animation';
import { BusinessError } from '@ohos.base';
// 下拉刷新状态枚举
export enum RefreshStatus {
IDLE = 'idle', // 空闲
PULLING = 'pulling', // 下拉中
REFRESHING = 'refreshing', // 刷新中
COMPLETED = 'completed' // 刷新完成
}
@Component
export struct RefreshLayout<T> {
@Prop controller: RefreshController = new RefreshController(); // 控制器
@BuilderParam content: () => T; // 子内容(List/Grid)
@BuilderParam refreshHeader?: () => void; // 自定义刷新头部(可选)
onRefresh: () => Promise<void>; // 刷新回调(需返回Promise)
private pullThreshold = 80; // 触发刷新的下拉阈值(vp)
private currentOffset = 0; // 当前下拉偏移量(vp)
@State status: RefreshStatus = RefreshStatus.IDLE;
build() {
Scroll() {
Column() {
// 刷新头部(根据状态显示/隐藏)
if (this.status !== RefreshStatus.IDLE || this.currentOffset > 0) {
this.refreshHeader ? this.refreshHeader() : this.defaultRefreshHeader();
}
// 子内容(List/Grid)
this.content();
}
}
.scrollDirection(ScrollDirection.Vertical)
.onScroll((scrollOffset) => {
// 仅处理下拉(scrollOffset为负表示下拉)
if (scrollOffset.y >= 0) {
this.currentOffset = 0;
this.status = RefreshStatus.IDLE;
return;
}
this.currentOffset = -scrollOffset.y;
// 状态切换:下拉中→刷新中(达到阈值且未刷新)
if (this.currentOffset >= this.pullThreshold && this.status === RefreshStatus.PULLING) {
this.status = RefreshStatus.REFRESHING;
// 触发刷新回调
this.onRefresh().then(() => {
// 刷新完成,恢复状态
this.status = RefreshStatus.COMPLETED;
// 1秒后隐藏头部
setTimeout(() => {
animateTo({ duration: 300 }, () => {
this.currentOffset = 0;
this.status = RefreshStatus.IDLE;
});
}, 1000);
});
} else if (this.status === RefreshStatus.IDLE) {
this.status = RefreshStatus.PULLING;
}
})
.onScrollEnd(() => {
// 未达到阈值,回弹
if (this.status !== RefreshStatus.REFRESHING) {
animateTo({ duration: 300 }, () => {
this.currentOffset = 0;
this.status = RefreshStatus.IDLE;
});
}
})
}
// 默认刷新头部(可替换为自定义布局)
@Builder
defaultRefreshHeader() {
Column({ space: 8 }) {
// 刷新图标(根据状态旋转)
Image($r('app.media.refresh_icon'))
.width(24)
.height(24)
.rotate({
angle: this.status === RefreshStatus.REFRESHING ? 360 : 0,
duration: this.status === RefreshStatus.REFRESHING ? 1000 : 0,
iterations: this.status === RefreshStatus.REFRESHING ? Infinity : 1
})
Text(this.getStatusText())
.fontSize(14)
.fontColor('#666666')
}
.width('100%')
.height(this.currentOffset)
.justifyContent(FlexAlign.Center)
}
// 根据状态获取提示文本
private getStatusText(): string {
switch (this.status) {
case RefreshStatus.PULLING:
return `下拉刷新(${Math.round(this.currentOffset)}/${this.pullThreshold}vp)`;
case RefreshStatus.REFRESHING:
return '正在刷新...';
case RefreshStatus.COMPLETED:
return '刷新完成';
default:
return '';
}
}
}
// 刷新控制器(可选:用于主动触发刷新)
export class RefreshController {
private refreshCallback?: () => void;
registerRefreshCallback(callback: () => void) {
this.refreshCallback = callback;
}
// 主动触发刷新
triggerRefresh() {
this.refreshCallback?.();
}
}
2. 组件中使用示例(适配 List)
import { RefreshLayout, RefreshController } from '../components/RefreshLayout';
@Entry
@Component
struct CustomRefreshDemo {
private refreshController = new RefreshController();
@State listData: string[] = Array.from({ length: 20 }, (_, i) => `列表项 ${i + 1}`);
build() {
Column() {
Text('自定义下拉刷新示例')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.padding(20)
// 自定义下拉刷新包裹List
RefreshLayout({
controller: this.refreshController,
onRefresh: this.fetchData // 刷新回调
}) {
// 子内容:List组件
List() {
ForEach(this.listData, (item) => {
ListItem() {
Text(item)
.width('100%')
.height(50)
.lineHeight(50)
.padding({ left: 20 })
}
})
}
.width('100%')
.height('100%')
}
}
.width('100%')
.height('100%')
}
// 模拟网络请求(刷新数据)
private async fetchData(): Promise<void> {
return new Promise((resolve) => {
// 模拟2秒请求耗时
setTimeout(() => {
// 新增数据
const newData = Array.from({ length: 5 }, (_, i) => `新列表项 ${i + 1}`);
this.listData = [...newData, ...this.listData];
resolve();
}, 2000);
});
}
}
三、避坑点
- 下拉阈值设置:建议 80~100vp,过小易误触,过大影响用户体验;
- 动画性能:旋转 / 位移动画需用
animateTo或rotate的duration属性,避免频繁状态更新导致卡顿; - 适配 Grid:直接将
List替换为Grid即可,RefreshLayout自动适配子组件; - 主动刷新:通过
RefreshController的triggerRefresh方法,可实现页面加载时主动触发刷新; - 弹性滑动:鸿蒙 6
Scroll组件默认支持弹性滑动,无需额外配置,低版本需开启edgeEffect: EdgeEffect.Spring。
更多关于HarmonyOS 鸿蒙Next 6 如何实现自定义下拉刷新组件(适配 List/Grid)?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
1、想分析了解原理自己动手实现自定义大致如下:
1)状态机管理
定义刷新状态机:IDLE(空闲)、DRAGGING(拖拽中)、DRAGGING_REFRESHABLE(可刷新)、REFRESHING(刷新中)、COMPLETE(完成)
2)触摸事件拦截
通过 onTouch 事件监听实现下拉距离检测:
3)自定义刷新组件
@Component export struct CustomRefresh { }
4)刷新动画头部自定义实现
@Component struct RefreshAnimHeader{ }
5)跨组件适配方案
可将List/Grid 统一封装在一个CustomRefresh组件中对外开放
6)弹性滑动支持
启用 List 的 EdgeEffect.Spring 属性
7)性能优化要点
结合 LazyForEach 进行懒加载渲染,优化长列表性能:
2、有开源的组件可直接使用: [https://ohpm.openharmony.cn/#/cn/detail/@abner%2Frefresh](https://ohpm.openharmony.cn/#/cn/detail/@abner%2Frefresh)
鸿蒙Next 6中,自定义下拉刷新组件主要通过Refresh组件和RefreshController实现。
在List或Grid组件外包裹Refresh组件,并绑定RefreshController。通过onRefreshing回调触发数据刷新逻辑,完成后调用controller.finishRefresh()结束。
可自定义Refresh的builder参数来修改刷新指示器样式,例如使用LoadingProgress或自定义布局。RefreshController用于控制刷新状态,如触发controller.startRefresh()。
此机制适用于List和Grid,无需依赖Java或C语言。

