鸿蒙NEXT开发动画案例2

1.创建空白项目

2.Page文件夹下面新建Spin.ets文件,代码如下:

复制代码
// ===== 接口定义(必须放在使用前)=====
/**
 * 关键帧动画整体配置参数
 */
interface KeyframeAnimationConfig {
  iterations: number;
  delay: number;
}

/**
 * 单个关键帧动画项
 */
interface KeyframeItem {
  duration: number;
  curve: Curve;
  event: () => void;
}

/**
 * 动画状态更新参数
 */
interface AnimationUpdateParams {
  scale1?: number;
  scale2?: number;
}
// ===== 接口定义结束 =====

/**
 * SpinKit 风格的弹性缩放加载动画组件。
 *
 * @component
 * @param spinSize - 动画容器大小(必须为正数)
 * @param spinColor - 动画颜色(支持资源引用)
 *
 * 示例:
 * ```ets
 * SpinTwo({
 *   spinSize: 60,
 *   spinColor: '#FF0000'
 * })
 * ```
 */
@ComponentV2
export struct SpinTwo {
  // 参数定义(父组件必须传入)
  @Require @Param spinSize: number = 48;
  @Require @Param spinColor: ResourceColor = '#209ED8';

  // 局部状态
  @Local scale1: number = 0;
  @Local scale2: number = 1;

  // 常量定义
  private readonly ANIMATION_DURATION: number = 1000;

  build() {
    Stack() {
      Canvas()
        .scale({ x: this.scale1, y: this.scale1 })
        .bounceStyle()

      Canvas()
        .scale({ x: this.scale2, y: this.scale2 })
        .bounceStyle()
    }
    .width(this.spinSize)
    .height(this.spinSize)
    .onAppear(() => {
      this.startAnimation();
    });
  }

  /**
   * 启动无限循环的关键帧动画
   */
  private startAnimation(): void {
    const uiContext = this.getUIContext();
    if (!uiContext) return;

    const animationConfig: KeyframeAnimationConfig = {
      iterations: -1,
      delay: 0
    };

    uiContext.keyframeAnimateTo(animationConfig, [
      this.createKeyframe(this.ANIMATION_DURATION, { scale1: 1, scale2: 0 }),
      this.createKeyframe(this.ANIMATION_DURATION, { scale1: 0, scale2: 1 })
    ]);
  }

  /**
   * 创建关键帧动画配置项
   * @param duration - 动画持续时间
   * @param update - 更新的状态对象
   */
  private createKeyframe(
    duration: number,
    update: AnimationUpdateParams
  ): KeyframeItem {
    return {
      duration,
      curve: Curve.EaseInOut,
      event: () => {
        if (update.scale1 !== undefined) this.scale1 = update.scale1;
        if (update.scale2 !== undefined) this.scale2 = update.scale2;
      }
    };
  }

  /**
   * 公共样式封装
   */
  @Styles
  bounceStyle() {
    .width('100%')
    .height('100%')
    .opacity(0.6)
    .borderRadius(this.spinSize / 2) // 圆形效果
    .backgroundColor(this.spinColor)
  }
}
复制代码
代码如下:
TypeScript 复制代码
// ===== 接口定义(必须放在使用前)=====
/**
 * 关键帧动画整体配置参数
 */
interface KeyframeAnimationConfig {
  iterations: number;
  delay: number;
}

/**
 * 单个关键帧动画项
 */
interface KeyframeItem {
  duration: number;
  curve: Curve;
  event: () => void;
}

/**
 * 动画状态更新参数
 */
interface AnimationUpdateParams {
  scale1?: number;
  scale2?: number;
}
// ===== 接口定义结束 =====

/**
 * SpinKit 风格的弹性缩放加载动画组件。
 *
 * @component
 * @param spinSize - 动画容器大小(必须为正数)
 * @param spinColor - 动画颜色(支持资源引用)
 *
 * 示例:
 * ```ets
 * SpinTwo({
 *   spinSize: 60,
 *   spinColor: '#FF0000'
 * })
 * ```
 */
@ComponentV2
export struct SpinTwo {
  // 参数定义(父组件必须传入)
  @Require @Param spinSize: number = 48;
  @Require @Param spinColor: ResourceColor = '#209ED8';

  // 局部状态
  @Local scale1: number = 0;
  @Local scale2: number = 1;

  // 常量定义
  private readonly ANIMATION_DURATION: number = 1000;

  build() {
    Stack() {
      Canvas()
        .scale({ x: this.scale1, y: this.scale1 })
        .bounceStyle()

      Canvas()
        .scale({ x: this.scale2, y: this.scale2 })
        .bounceStyle()
    }
    .width(this.spinSize)
    .height(this.spinSize)
    .onAppear(() => {
      this.startAnimation();
    });
  }

  /**
   * 启动无限循环的关键帧动画
   */
  private startAnimation(): void {
    const uiContext = this.getUIContext();
    if (!uiContext) return;

    const animationConfig: KeyframeAnimationConfig = {
      iterations: -1,
      delay: 0
    };

    uiContext.keyframeAnimateTo(animationConfig, [
      this.createKeyframe(this.ANIMATION_DURATION, { scale1: 1, scale2: 0 }),
      this.createKeyframe(this.ANIMATION_DURATION, { scale1: 0, scale2: 1 })
    ]);
  }

  /**
   * 创建关键帧动画配置项
   * @param duration - 动画持续时间
   * @param update - 更新的状态对象
   */
  private createKeyframe(
    duration: number,
    update: AnimationUpdateParams
  ): KeyframeItem {
    return {
      duration,
      curve: Curve.EaseInOut,
      event: () => {
        if (update.scale1 !== undefined) this.scale1 = update.scale1;
        if (update.scale2 !== undefined) this.scale2 = update.scale2;
      }
    };
  }

  /**
   * 公共样式封装
   */
  @Styles
  bounceStyle() {
    .width('100%')
    .height('100%')
    .opacity(0.6)
    .borderRadius(this.spinSize / 2) // 圆形效果
    .backgroundColor(this.spinColor)
  }
}

3.修改Index.ets文件,代码如下:

复制代码
import { SpinTwo } from './Spin';

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';

  build() {
    Column() {
      SpinTwo({
        spinSize: 60,
        spinColor: '#FF0000'
      })
    }
    .alignItems(HorizontalAlign.Center)
    .justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%')
  }
}

代码如下:

TypeScript 复制代码
import { SpinTwo } from './Spin';

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';

  build() {
    Column() {
      SpinTwo({
        spinSize: 60,
        spinColor: '#FF0000'
      })
    }
    .alignItems(HorizontalAlign.Center)
    .justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%')
  }
}

4.运行项目,登录华为账号,需进行签名

5.动画效果如下:

相关推荐
安卓开发者7 小时前
鸿蒙NEXT应用数据持久化全面解析:从用户首选项到分布式数据库
数据库·分布式·harmonyos
雅雅姐7 小时前
Android14 init.rc中on boot阶段操作4
android
fatiaozhang95278 小时前
中国移动中兴云电脑W132D-RK3528-2+32G-刷机固件包(非原机制作)
android·xml·电脑·电视盒子·刷机固件·机顶盒刷机
森之鸟8 小时前
开发中使用——鸿蒙播放本地mp3文件
华为·harmonyos
不是三毛没有半9 小时前
华为USG6000v2 NAT模式下IPSEC IKE V1 实验
网络·网络安全·华为
前端世界9 小时前
HarmonyOS 数据处理性能优化:算法 + 异步 + 分布式实战
算法·性能优化·harmonyos
Android出海10 小时前
Google Play账户与App突遭封禁?紧急应对与快速重构上架策略
android·网络·重构·新媒体运营·产品运营·内容运营
恋猫de小郭10 小时前
Flutter 官方 LLM 动态 UI 库 flutter_genui 发布,让 App UI 自己生成 UI
android·前端·flutter
HarmonyOS小助手10 小时前
【案例+1】HarmonyOS官方模板优秀案例 第7期:金融理财 · 记账应用
harmonyos·鸿蒙·鸿蒙生态
锅拌饭11 小时前
saveEnabled导致的Fragment大量泄露
android