HarmonyOS鸿蒙Next组件/模板集成创新活动-巡星漫游指南

HarmonyOS鸿蒙Next组件/模板集成创新活动-巡星漫游指南

引言

在HarmonyOS应用开发中,清晰的新用户引导和高效的开发工具集成至关重要。本文分享了在HarmonyOS Next 5.0+环境中,集成第三方高亮引导组件和常用工具组件的实践经验,并介绍了一个自主开发的基于ArkTS的高性能动画轻便框架,以提升ArkUI当前在Canvas动画方面的能力。这些实践不仅提升了应用的用户体验,也为HarmonyOS开发者社区提供了有益参考。 https://appgallery.huawei.com/app/detail?id=com.SHKJ.TYDF.Guide&channelId=SHARE,APP中的动画效果全部应用了自主开发动画框架的,也请大家多多提意见。

1 第三方组件集成指南

1.1 组件集成方式

在HarmonyOS应用开发中,集成第三方组件主要有两种便捷方式:

命令行集成:使用ohpm(OpenHarmony Package Manager)命令行工具直接安装所需组件。例如,执行以下命令即可完成高亮引导组的集成:

ohpm install [@ohos](/user/ohos)/high_light_guide

组件市场集成:通过DevEco Studio的组件市场可视化集成:

  1. 首先安装组件市场插件(需先安装组件市场插件)组件市场安装与使用说明

  2. 在IDE中选择Tools标签,进入Component Market

    图片

  3. 搜索所需组件名称,点击页面内"Integrate SDK"即可完成集成

    图片

集成成功后,组件将出现在项目的oh_modules目录中,配置文件会自动更新同步。在代码中只需通过import语句引入即可使用:

import * as HighLight from '[@ohos](/user/ohos)/high_light_guide';
import * as Utils from '@pura/harmony-utils';

1.2 组件使用与进阶技巧

@ohos/high_light_guide使用方式,指导文档https://developer.huawei.com/consumer/cn/doc/architecture-guides/feature_guide_page-0000002271957437

高亮引导组件的增强使用:虽然组件作者声称当前不支持隐藏后再次显示,但通过分析源代码,我们在Controller类中添加了两个方法解决了这一限制:

public hide():void{
  this.isGuideShowing = false;
  if (this.observedMaskVisibleState) {
    this.observedMaskVisibleState.setShowGuideMask(false);
  }
}
  public showPageAgain(page:number){
    LogUtil.info('Controller show');
    // 组件名称必须设置,如果没有设置,则不显示组件引导页
    if (!this.builder || !this.builder.label) {
      hilog.info(0x0000, GlobalContext.HILOG_TAG, '%{public}s', 'label is empty, please set the label for guide');
      return;
    }
    
    
    if (!this.builder || !this.builder.guidePages || this.builder.guidePages.length === 0) {
      hilog.info(0x0000, GlobalContext.HILOG_TAG, '%{public}s', 'There is no guide to show !! Please add at least one page.');
      return;
    }

    if (this.observedMaskVisibleState) {
      this.observedMaskVisibleState.setShowGuideMask(true);
    }

    this.isGuideShowing = true;
    this.guidePageIndex = page;
    this.showGuidePage();

    if (this.builder && this.builder.visibleStateChangeListener) {
      this.builder.visibleStateChangeListener.onShowed(this);
    }
  }

调用方式简单明了:

import * as HighLight from '[@ohos](/user/ohos)/high_light_guide';
......
private controller: HighLight.Controller | null = null;
......
this.controller?.hide();
setTimeout(() => {
  this.controller?.showPageAgain(1);
}, 10000)

工具组件的高效应用:常用工具组件的使用非常直观,例如显示一个确认弹窗:在导入文件后直接调取静态方法,比如一个确认弹窗

import * as Utils from '@pura/harmony-utils'
setTimeout(() => { 
  Util.DialogUtil.showConfirmDialog({
    title: '测试确认标题',
    message: '测试确认信息',
    onAction:()=>{
        Util.DialogUtil.showConfirmDialog({
          title: '测试确认标题1',
          message: '测试确认信息1',
      })
    }
  })
}
, 1000)

2 高性能动画框架开发

2.1 框架架构设计

以提升ArkUI当前在Canvas动画方面的能力,开发了一个高性能的动画帧控制与动画实时生成框架。该框架采用分层架构设计,包括:

  • 动画管理层:负责动画的创建、暂停、恢复和销毁
  • 帧数据处理层:生成和处理中间帧数据,支持多种动画曲线
  • 渲染控制层:控制渲染时序,确保动画流畅性

2.2 核心功能特性

框架提供了丰富的动画功能:

  • 多动画曲线支持:支持线性、缓入、缓出、缓入缓出、摩擦等多种内置动画曲线,同时也支持自定义曲线函数。
  • 精准的帧率控制:通过设置expectedFrameRateRange参数,可以精确控制动画的播放帧率。
  • 动画生命周期管理:提供完整的动画控制方法,包括暂停(pause)、恢复(resume)、清除(clear)和重置(reset)等。
  • 并发动画支持:通过UUID标识符管理多个并发动画,可以对同一组动画进行统一控制。

2.3 技术实现亮点

帧数据处理:框架能够根据动画参数(持续时间、播放速度、曲线类型等)自动生成所有中间帧数据,并通过定时器逐帧执行动画。

时间容差机制:引入了半周期容差机制(halfIntervalTime),确保动画帧在适当的时间范围内执行,避免了因定时器精度问题导致的动画跳帧现象。

内存优化管理:通过队列数据结构管理动画任务,及时清理已完成动画,避免内存泄漏。

框架图如下:

图片

代码如下:

import { HashMap, Queue } from "[@kit](/user/kit).ArkTS";
import { systemDateTime } from "[@kit](/user/kit).BasicServicesKit";
import { hilog } from "[@kit](/user/kit).PerformanceAnalysisKit";
import { curves } from "[@kit](/user/kit).ArkUI";

export interface FrameStatus {
  startValue: number,
  targetValue: number,
  incrementalValue?: number

}

interface AnimationFrame {
  frameData: FrameStatus[],
  frameSequenceTime: number,
}

interface AnimationFrameTask {
  frame: AnimationFrame,
  task: () => void,
}

interface Animation {
  isPaused: boolean,
  startTime: number,
  endTime: number,
  tasks: Queue<AnimationFrameTask>
}

export class AnimateToParam {
  duration?: number = 1000; // 动画持续时间,单位为毫秒。
  tempo?: number = 1; // 播放速度倍速。
  curve?: Curve = Curve.Linear; // 动画曲线
  curveFunc?: (time: number) => number = undefined; // 自定义动画曲线,根据相对时间变化返回取得曲线值
  delay?: number = undefined; // 延迟 ,毫秒为单位
  iterations?: number = 1; // 播放次数
  onFinish?: () => void = undefined; // 动画播放完成回调。
  expectedFrameRateRange?: number = 30; // 期望帧率
}

export default class AnimateFactory {
  private timerID: number | undefined = undefined;
  //执行周期时间,毫秒单位
  private intervalTime = 10;
  //执行半周期时间,毫秒单位
  private halfIntervalTime = 5;
  // 动画管理列表
  private animations: HashMap<string, Animation> = new HashMap<string, Animation>();
  // 待清除列表
  private removeList: Queue<string> = new Queue();
  // 同步渲染控制
  private isRendering = false;

  /**
   * 生成动画数据方法
   *
   * 该方法根据提供的动画参数生成中间帧数据,并通过定时器逐帧执行动画。
   * 支持自定义动画曲线、播放速度、延迟、多次迭代等特性。
   * [@param](/user/param) UUID - 同UUID序列动画,用于同一个对象或一组对象共同触发的动画,暂停,终止。
   * [@param](/user/param) param - 动画参数对象,包含持续时间、速度、曲线等配置
   * [@param](/user/param) lastFrameList - 动画对象的初始状态和目标值列表
   * [@param](/user/param) updateStatus - 状态更新回调函数,在每帧执行时调用
   * [@param](/user/param) animateRender - 渲染回调函数,在每帧执行时调用
   *
   * [@example](/user/example)
   * // 基本用法
   * animateTo(
   *   new animateToParam({ duration: 2000 }),
   *   [{ startValue: pos.x, targetValue: pos.x + 100 }],
   *   (newStatus)=>{newStatus.forEach((item)=>{
   *      console.log(`状态更新:${item.targetValue.toString()},变化量:${item.incrementalValue}`)}
   *   )},
   *  () => { //动画渲染
   *    this.frameCtrl.listening({
   *      moduleName: 'draw',
   *       module: () => {
   *         this.drawMain()
   *      }
   *    })
   *  }
   * );
   */
  public animateTo(
    UUID: string, // 同UUID序列,暂停,终止。
    param: AnimateToParam, // 动画参数
    lastFrameList: FrameStatus[], // 更新对象与目标值列表
    updateStatus: (newStatus: FrameStatus[]) => void, // 更新复杂状态回调
    animateRender: () => void // 动画渲染回调
  ) {

    // 融合默认参数
    const defaultParam = new AnimateToParam();
    for (const key of Object.keys(param)) {
      if (Reflect.get(param, key)!== undefined && Reflect.get(param, key)!== null ) {
        Reflect.set(defaultParam, key, Reflect.get(param, key));
      }
    }

    this.intervalTime = 1000 / (param.expectedFrameRateRange || 30);

    // ============== 根据param生成和lastFrameList,每一帧的数据 ==============
    // 计算实际动画持续时间(考虑播放速度)
    const actualDuration = (defaultParam.duration!) / defaultParam.tempo!;
    // 计算帧间隔时间(基于期望帧率)
    const frameInterval = 1000 / defaultParam.expectedFrameRateRange!;
    this.intervalTime = frameInterval;
    this.halfIntervalTime = this.intervalTime / 2
    // 计算总帧数(向上取整确保覆盖完整持续时间)
    const totalFrames = Math.ceil(actualDuration / frameInterval);
    // 创建存储所有动画帧数据的数组
    const animationFrames: AnimationFrame[] = new Array<AnimationFrame>(totalFrames);

    // 获取曲线函数
    const curveFunction = defaultParam.curveFunc || getCurveFunction(defaultParam.curve!);

    // 生成每一帧数据
    for (let frameIdx = 0; frameIdx < totalFrames; frameIdx++) {
      // 计算当前进度(0到1之间)
      const progress = frameIdx / (totalFrames - 1);
      // 应用动画曲线变换进度值
      const curvedProgress = curveFunction(progress);


      // 生成每帧数据
      const frameData: FrameStatus[] = lastFrameList.map((item, index) => {
        const startValue = item.startValue;
        const endValue = item.targetValue;
        const newStartValue = animationFrames[frameIdx -1]?.frameData[index].targetValue || startValue;
        // 线性插值计算当前值
        const targetValue = startValue + (endValue - startValue) * curvedProgress;
        // 线性插值计算当前变化值
        const incrementalValue = targetValue - newStartValue
        return {
          startValue: newStartValue,
          targetValue: targetValue,
          incrementalValue: incrementalValue
        } as FrameStatus;
      });

      // 添加到帧列表(包含帧显示时间)
      animationFrames[frameIdx] = {
        frameData: frameData,
        frameSequenceTime: (frameIdx * frameInterval) + (defaultParam.delay || 0)
      };
    }

    // 处理多次迭代
    if (defaultParam.iterations! > 1) {
      const baseFrames = [...animationFrames];
      const lastFrameTime = baseFrames[baseFrames.length - 1].frameSequenceTime;

      for (let iter = 1; iter < defaultParam.iterations!; iter++) {
        const offset = iter * lastFrameTime;
        const iterFrames = baseFrames.map(frame => ({
          frameData: frame.frameData,
          frameSequenceTime: frame.frameSequenceTime + offset
        } as AnimationFrame));

        animationFrames.push(...iterFrames);
      }
    }
    // ============== 帧数据生成完成 ==============

    // 打开一个animations列队逐帧生成执行动画任务
    const tasks: Queue<AnimationFrameTask> = new Queue<AnimationFrameTask>();
    animationFrames.forEach((item, index) => {
      const task: AnimationFrameTask = {
        frame: item,
        task: () => {
          // 执行回调刷新状态数据,
          updateStatus(item.frameData);
          // 触发渲染
          animateRender();
          // 动画结束时执行完成回调,提前10帧进行倒计时
          if (index === animationFrames.length - 10) {
            setTimeout(()=>{
              defaultParam.onFinish?.();
            },(item.frameSequenceTime - (defaultParam?.delay || 0))/index*10)
          }
        },
      };
      // 组成一组动画列任务序列
      tasks.add(task);
    })
    const startTime = systemDateTime.getTime() + tasks.getFirst().frame.frameSequenceTime;
    const endTime = startTime + animationFrames[animationFrames.length-1].frameSequenceTime
    // 加入动画管理列表
    this.animations.set(UUID, {
      isPaused: false,
      startTime,
      endTime,
      tasks
    });
    // 启动动画播放
    this.makeFrameLoop();
  }

  private makeFrameLoop() {
    if (this.timerID !== undefined) {
      return;
    }
    // 定期执行生成的动画
    this.timerID = setInterval(() => {
      if (this.animations.length === 0) {
        clearInterval(this.timerID);
        this.timerID = undefined;
        return;
      }
      if (this.isRendering) {
        return;
      }
      this.isRendering = true;
      this.animations.forEach((animation, UUID) => {
        try {
          const now = systemDateTime.getTime();
          // 跳过空动画或暂停状态
          if (!animation || animation.isPaused) {
            return;
          }
          // 检查动画是否过期,已释放
          if ((animation.endTime < now || animation.tasks.length === 0) && UUID) {
            this.removeList.add(UUID);
            return;
          }
          // 检查当前帧是否到达执行时间(带半周期容差),每次拿一帧出来执行
          const nextTask = animation.tasks.getFirst();
          const shouldExecute =
            (nextTask.frame.frameSequenceTime + animation.startTime) <= (now + this.halfIntervalTime)
          if (shouldExecute) {
            const frame = animation.tasks.pop();
            frame?.task();
          }

        } catch (e) {
          hilog.error(0x0000, 'AnimateFactory.Interval',
            `动画数据生成异常时间${Date.now().toLocaleString()}${JSON.stringify(e)}`)
        }
      });
      while (this.removeList.length > 0) {
        const UUID = this.removeList.pop();
        this.animations.remove(UUID);
      }
      this.isRendering = false;
    }, this.intervalTime)
  }

  // 暂停指定UUID动画
  public pause(UUID: string) {
    const animation = this.animations.get(UUID);
    if (animation) {
      animation.isPaused = true;
    }
  }

  // 恢复指定UUID动画
  public resume(UUID: string) {
    const animation = this.animations.get(UUID);
    if (animation) {
      animation.isPaused = false;
      const startTime = systemDateTime.getTime();
      const endTime = (animation.endTime - animation.startTime) + systemDateTime.getTime();
      animation.startTime = startTime;
      animation.endTime = endTime;
    }
  }

  // 清除指定UUID动画
  public clear(UUID: string) {
    if (this.animations.hasKey(UUID)) {
      this.animations.get(UUID).isPaused = true;
      this.animations.remove(UUID);
    }
  }
  // 清除指定名称动画
  public clearEndsOfName(name: string) {
    const filteredKeys = Array.from(this.animations.keys()).filter(key =>
    key.

更多关于HarmonyOS鸿蒙Next组件/模板集成创新活动-巡星漫游指南的实战教程也可以访问 https://www.itying.com/category-93-b0.html

2 回复

HarmonyOS鸿蒙Next组件/模板集成创新活动-巡星漫游指南是一项面向开发者的技术活动,旨在推广鸿蒙Next的组件和模板集成能力。活动鼓励开发者探索鸿蒙系统的创新应用,通过集成现有组件和模板提升开发效率。参与者可以体验鸿蒙Next的最新特性,如分布式架构和跨设备协同,并有机会展示自己的创新成果。该活动不涉及Java或C语言,专注于鸿蒙自身的开发框架和工具。

更多关于HarmonyOS鸿蒙Next组件/模板集成创新活动-巡星漫游指南的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


感谢分享在HarmonyOS Next 5.0+环境下集成第三方组件和开发高性能动画框架的实践经验。内容详实,技术方案清晰,对开发者社区有很好的参考价值。

关于高亮引导组件的集成和扩展,通过分析源码并添加hide()showPageAgain()方法解决了隐藏后再次显示的限制,这种思路很实用。命令行和组件市场两种集成方式也覆盖了不同开发场景的需求。

自主研发的动画框架架构设计合理,分层明确,支持多曲线类型和精准帧率控制,尤其是时间容差机制和内存管理策略,能有效提升动画流畅性和稳定性。使用UUID管理并发动画、队列结构清理资源,都是性能优化的关键点。

提供的代码示例和最佳实践建议(如冷启动延迟显示、合理设置帧率、及时释放资源)非常贴合实际开发需求,有助于其他开发者避免常见陷阱。

整体来看,这是一次成功的HarmonyOS生态实践,既利用了现有组件能力,又通过自主创新弥补了平台当前的动画能力缺口,值得推广和学习。

回到顶部