在这里插入图片描述
在这里插入图片描述

一、引言

动画是提升应用体验的关键因素。一个优秀的动画不仅能让界面更加生动,还能引导用户注意力、传达状态变化、增强操作反馈。HarmonyOS ArkUI 提供了强大的动画能力,包括隐式动画、显式动画、转场动画、关键帧动画、弹簧动画等。

本文将以一个霓虹暗色风格的动画演示页面为主线,深入讲解 ArkUI 动画体系的各个层次,通过大量代码示例帮助读者掌握动画开发的核心技能。

二、动画的分类

ArkUI 的动画体系可以从多个维度分类:

分类维度 类型 说明
触发方式 隐式动画 属性变化自动过渡
触发方式 显式动画 闭包内状态变化生效
作用对象 属性动画 对单个属性做动画
作用对象 转场动画 组件插入/移除过渡
控制方式 关键帧动画 多段关键帧控制
物理模型 弹簧动画 模拟物理弹性

2.1 隐式动画(animation)

隐式动画通过 animation 属性声明,当组件的属性(如位置、大小、颜色、透明度)发生变化时,自动以动画形式过渡:

Text('Hello')
  .scale({ x: this.scale, y: this.scale })
  .animation({
    duration: 1000,       // 动画时长(毫秒)
    curve: Curve.EaseInOut, // 动画曲线
    delay: 0,             // 延迟时间
    iterations: 1,        // 迭代次数
    playMode: PlayMode.Normal // 播放模式
  })

2.2 显式动画(animateTo)

显式动画通过 animateTo 函数触发,在闭包内修改状态,框架自动为这些状态变化添加动画:

animateTo({ duration: 1000 }, () => {
  this.scale = 1.5;  // 闭包内的状态变化会带动画
});

2.3 隐式与显式的区别

特性 隐式动画 显式动画
声明位置 组件属性上 代码块中
触发方式 属性变化自动触发 调用 animateTo 触发
控制粒度 单个组件 闭包内所有变化
使用场景 单个属性动画 多个属性/组件联动

三、动画参数详解

3.1 动画时长

duration: 1000  // 单位毫秒,1000 = 1 秒

3.2 动画曲线

动画曲线(curve)控制动画的速度变化,ArkUI 提供多种内置曲线:

Curve.Linear        // 匀速
Curve.Ease          // 慢-快-慢
Curve.EaseIn        // 加速
Curve.EaseOut       // 减速
Curve.EaseInOut     // 慢-快-慢(更平滑)
Curve.FastOutSlowIn // 快出慢入
Curve.SpringCurve   // 弹簧曲线

3.3 播放模式

PlayMode.Normal    // 正常播放
PlayMode.Reverse   // 反向播放
PlayMode.Alternate // 交替播放(正-反-正)
PlayMode.AlternateReverse // 交替反向播放

3.4 迭代次数

iterations: 1      // 播放 1 次
iterations: 3      // 播放 3 次
iterations: -1     // 无限循环

四、实战代码:霓虹暗色动画页面

下面我们实现一个霓虹风格的动画演示页面,包含层叠动画区和动画 API 速查表。

4.1 定义数据结构

interface AnimRow {
  param: string;
  value: string;
}

代码说明:

AnimRow 接口描述动画 API 速查表中的一行数据,包含 API 名称和说明。

4.2 组件状态定义

@Entry
@Component
struct AnimationPage {
  @State scale: number = 1;
  @State rotate: number = 0;
  @State opacity: number = 1;
  @State playing: boolean = false;
  @State animRows: AnimRow[] = [
    { param: 'animation', value: '隐式动画,属性变化自动过渡' },
    { param: 'animateTo', value: '显式动画,闭包内状态变化生效' },
    { param: 'transition', value: '组件插入/移除转场' },
    { param: 'keyframeAnimateTo', value: '关键帧动画,多段控制' },
    { param: 'springMotion', value: '弹簧物理动画' }
  ];

代码说明:

  • @State scale:缩放比例,控制方块大小。
  • @State rotate:旋转角度,控制方块旋转。
  • @State opacity:透明度,控制方块透明程度。
  • @State playing:动画播放状态标志。
  • @State animRows:动画 API 速查表数据。

4.3 动画控制方法

togglePlay(): void {
  this.playing = !this.playing;
  if (this.playing) {
    animateTo({ duration: 1500, curve: Curve.EaseInOut, iterations: -1, playMode: PlayMode.Alternate }, () => {
      this.scale = 1.6;
      this.rotate = 360;
      this.opacity = 0.4;
    });
  } else {
    animateTo({ duration: 500, curve: Curve.EaseOut }, () => {
      this.scale = 1;
      this.rotate = 0;
      this.opacity = 1;
    });
  }
}

代码说明:

togglePlay 方法是动画控制的核心:

  1. 切换状态this.playing = !this.playing 切换播放/停止状态。

  2. 播放动画:调用 animateTo 显式动画,参数说明:

    • duration: 1500:动画时长 1.5 秒。
    • curve: Curve.EaseInOut:缓入缓出曲线。
    • iterations: -1:无限循环。
    • playMode: PlayMode.Alternate:交替播放,动画会在正反两个方向来回切换,形成"呼吸"效果。
  3. 动画目标:闭包内同时修改三个属性:

    • this.scale = 1.6:方块放大到 1.6 倍。
    • this.rotate = 360:方块旋转 360 度。
    • this.opacity = 0.4:方块透明度降低到 40%。
  4. 停止动画:调用 animateTo 将属性恢复初始值,duration: 500 用较短时间快速复位。

  5. 关键理解animateTo 闭包内的所有状态变化都会被包装成动画,这是与隐式动画(单属性)最大的区别。

4.4 构建 UI:层叠动画区

build() {
  Scroll() {
    Column({ space: 16 }) {
      // 顶部霓虹标题
      Column() {
        Text('ANIMATION')
          .fontSize(12)
          .fontColor('#00F5FF')
          .letterSpacing(6)
        Text('动画与转场')
          .fontSize(26)
          .fontWeight(FontWeight.Bold)
          .fontColor(Color.White)
          .margin({ top: 6 })
        Text('属性动画 · 显式动画 · 关键帧')
          .fontSize(12)
          .fontColor('#00F5FF')
          .margin({ top: 6 })
      }
      .width('100%')
      .padding({ top: 48, bottom: 30 })
      .backgroundColor('#0A0A0F')

      // Stack 层叠动画区
      Stack({ alignContent: Alignment.Center }) {
        // 底层霓虹圆
        Circle({ width: 180, height: 180 })
          .fill('none')
          .stroke('#00F5FF')
          .strokeWidth(2)
          .opacity(0.6)
          .shadow({ radius: 30, color: '#8800F5FF' })
        // 中层光环
        Circle({ width: 120, height: 120 })
          .fill('none')
          .stroke('#FF2BD6')
          .strokeWidth(1.5)
          .opacity(0.5)
          .shadow({ radius: 24, color: '#88FF2BD6' })
        // 上层方块
        Column() {
          Text('NEON')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor('#00F5FF')
        }
        .width(80)
        .height(80)
        .justifyContent(FlexAlign.Center)
        .backgroundColor('#111122')
        .borderRadius(12)
        .border({ width: 2, color: '#00F5FF' })
        .shadow({ radius: 20, color: '#AA00F5FF' })
        .scale({ x: this.scale, y: this.scale })
        .rotate({ angle: this.rotate })
        .opacity(this.opacity)
      }
      .width('100%')
      .height(260)
      .backgroundColor('#0A0A0F')

代码说明:

层叠动画区使用 Stack 布局,实现三层元素的叠加:

  1. Stack 布局Stack({ alignContent: Alignment.Center }) 将所有子元素居中对齐堆叠,后面的元素覆盖在前面的元素上。

  2. 底层霓虹圆Circle 组件绘制圆形,fill('none') 表示不填充,stroke('#00F5FF') 设置青色描边,shadow 添加霓虹发光效果。

  3. 中层光环:粉色描边圆环,颜色与底层形成对比,增加层次感。

  4. 上层方块:这是动画作用的核心元素:

    • .scale({ x: this.scale, y: this.scale }):绑定缩放状态。
    • .rotate({ angle: this.rotate }):绑定旋转状态。
    • .opacity(this.opacity):绑定透明度状态。
    • togglePlay 修改这三个状态时,方块会同步进行缩放、旋转和透明度变化的组合动画。
  5. 霓虹风格:青色和粉色描边配合强发光阴影(shadowcolor 带高透明度),在黑色背景上形成霓虹灯效果。

      // 发光按钮
      Button(this.playing ? '停止动画' : '播放动画')
        .width('100%')
        .height(50)
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .fontColor('#0A0A0F')
        .backgroundColor('#00F5FF')
        .borderRadius(25)
        .shadow({ radius: 24, color: '#AA00F5FF', offsetY: 0 })
        .onClick(() => { this.togglePlay(); })

代码说明:

播放/停止按钮采用霓虹发光风格:

  • 背景色为青色 #00F5FF,文字为深色,形成高对比。
  • .shadow({ radius: 24, color: '#AA00F5FF' }) 设置大范围发光阴影,让按钮看起来像霓虹灯管。
  • 按钮文字根据 playing 状态动态切换。
      // 动画参数暗色表格
      Column() {
        Text('动画 API 速查')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .fontColor('#00F5FF')
          .alignSelf(ItemAlign.Start)
          .margin({ bottom: 8 })
        ForEach(this.animRows, (row: AnimRow) => {
          Row({ space: 10 }) {
            Text(row.param)
              .fontSize(12)
              .fontWeight(FontWeight.Bold)
              .fontColor('#FF2BD6')
              .width(130)
              .fontFamily('monospace')
            Text(row.value)
              .fontSize(11)
              .fontColor('#BBBBBB')
              .layoutWeight(1)
          }
          .width('100%')
          .padding({ top: 10, bottom: 10 })
          .border({ width: { bottom: 1 }, color: '#1F1F2E' })
        })
      }
      .width('100%')
      .padding(16)
      .backgroundColor('#111122')
      .borderRadius(14)
      .border({ width: 1, color: '#00F5FF44' })

代码说明:

动画 API 速查表采用暗色表格风格:

  • 表头使用青色文字,与页面主色调一致。
  • API 名称使用粉色等宽字体,突出技术感。
  • 说明文字使用灰色。
  • 行与行之间用深色边框分隔,整体融入暗色背景。

五、转场动画

5.1 组件转场(transition)

转场动画用于组件插入和移除时的过渡效果:

if (this.show) {
  Column() {
    Text('新内容')
  }
  .transition(
    TransitionEffect.OPACITY
      .combine(TransitionEffect.translate({ y: 100 }))
      .animation({ duration: 300 })
  )
}

代码说明:

  • TransitionEffect.OPACITY:透明度转场效果。
  • .combine():组合多个转场效果。
  • .translate({ y: 100 }):位移转场效果。
  • show 为 true 时组件淡入并上移,为 false 时淡出并下移。

5.2 页面转场

页面跳转时可以设置转场动画:

router.pushUrl({
  url: 'pages/DetailPage',
  animation: {
    duration: 300,
    curve: Curve.EaseInOut
  }
});

六、关键帧动画

关键帧动画允许精确控制动画在不同时间点的状态:

keyframeAnimateTo({
  duration: 2000,
  iterations: 1,
  keyframes: [
    { duration: 0.25, event: () => { this.x = 100; this.y = 0; } },
    { duration: 0.25, event: () => { this.x = 100; this.y = 100; } },
    { duration: 0.25, event: () => { this.x = 0; this.y = 100; } },
    { duration: 0.25, event: () => { this.x = 0; this.y = 0; } }
  ]
});

代码说明:

  • keyframeAnimateTo 是显式关键帧动画 API。
  • keyframes 数组定义多个关键帧,每个关键帧包含 duration(占动画总时长的比例)和 event(该时刻的状态修改闭包)。
  • 上面的例子让元素沿正方形路径运动一圈。

七、弹簧动画

弹簧动画模拟物理弹簧的弹性效果,让动画更自然:

import { curves } from '@kit.ArkUI';

// 方式一:使用弹簧曲线
animateTo({
  curve: curves.springMotion(0.5, 0.8)
}, () => {
  this.scale = 1.5;
});

// 方式二:使用弹簧动画
this.scale = 1.5;
animateTo({
  curve: curves.springCurve(0.4, 1.0)
}, () => {
  this.scale = 1.0;
});

代码说明:

  • curves.springMotion(response, dampingFraction):弹簧运动曲线,response 控制响应速度,dampingFraction 控制阻尼。
  • 弹簧动画在到达目标值后会有回弹效果,模拟真实物理运动。

八、动画性能优化

8.1 使用 transform 属性

动画尽量使用 transform 相关属性(scalerotatetranslate),这些属性不触发布局重排,性能更好:

// 推荐:transform 动画
.translate({ x: this.offsetX })
.scale({ x: this.scale })

// 不推荐:修改布局属性
.width(this.newWidth)

8.2 避免动画大量组件

对大量组件同时做动画会消耗大量性能,应尽量只对必要组件做动画。

8.3 合理设置动画时长

动画时长过短会显得生硬,过长会让用户等待。一般交互反馈 200-300ms,转场 300-500ms,强调动画 500-1000ms。

九、常见问题

9.1 动画不生效

原因:可能是隐式动画的 animation 属性声明位置错误,或状态修改方式不正确。

解决:确认 animation 声明在需要动画的属性之后;使用 animateTo 时确认状态在闭包内修改。

9.2 动画循环不停止

原因iterations: -1 表示无限循环。

解决:在需要停止时重新设置状态并调用 animateTo 复位。

9.3 动画卡顿

原因:动画了过多组件或使用非 transform 属性。

解决:优化动画对象,使用 transform 属性。

十、总结

本文深入讲解了 HarmonyOS 动画与转场技术,通过一个霓虹暗色风格的演示页面实战演示了显式动画、属性动画等核心能力。

核心要点回顾:

  1. 隐式动画(animation)用于单属性自动过渡。
  2. 显式动画(animateTo)用于闭包内多属性联动。
  3. 动画参数包括时长、曲线、播放模式、迭代次数。
  4. 转场动画(transition)用于组件插入/移除。
  5. 关键帧动画(keyframeAnimateTo)精确控制时间点状态。
  6. 弹簧动画模拟物理弹性,更自然。
  7. 性能优化:使用 transform 属性,避免动画大量组件。

动画是提升用户体验的重要工具,掌握它能让我们构建出更生动、更有质感的应用。下一篇我们将讲解 HarmonyOS 多媒体开发。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐