HarmonyOS 鸿蒙Next 6 如何实现自定义下拉刷新组件(适配 List/Grid)?

HarmonyOS 鸿蒙Next 6 如何实现自定义下拉刷新组件(适配 List/Grid)?

问题描述

鸿蒙 6 原生List组件的下拉刷新(onRefresh)样式固定,无法满足产品自定义需求(如修改刷新图标、添加加载动画、调整下拉距离)。如何实现通用的自定义下拉刷新组件,同时适配ListGrid组件,且支持鸿蒙 6 的弹性滑动效果?关键字:鸿蒙 6、自定义下拉刷新、List/Grid 适配、弹性滑动、Stage 模型、API20

3 回复

一、原理解析

自定义下拉刷新的核心是利用鸿蒙的Scroll组件 +AnimatableProperty动画,实现逻辑:

  1. 外层用Scroll组件包裹List/Grid,监听onScroll事件获取下拉距离;
  2. 根据下拉距离分状态:默认状态→下拉中→刷新中→刷新完成;
  3. 通过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);
    });
  }
}

三、避坑点

  1. 下拉阈值设置:建议 80~100vp,过小易误触,过大影响用户体验;
  2. 动画性能:旋转 / 位移动画需用animateTorotateduration属性,避免频繁状态更新导致卡顿;
  3. 适配 Grid:直接将List替换为Grid即可,RefreshLayout自动适配子组件;
  4. 主动刷新:通过RefreshControllertriggerRefresh方法,可实现页面加载时主动触发刷新;
  5. 弹性滑动:鸿蒙 6Scroll组件默认支持弹性滑动,无需额外配置,低版本需开启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实现。

ListGrid组件外包裹Refresh组件,并绑定RefreshController。通过onRefreshing回调触发数据刷新逻辑,完成后调用controller.finishRefresh()结束。

可自定义Refreshbuilder参数来修改刷新指示器样式,例如使用LoadingProgress或自定义布局。RefreshController用于控制刷新状态,如触发controller.startRefresh()

此机制适用于ListGrid,无需依赖Java或C语言。

回到顶部