# ✨ 零基础学 ArkUI 动画(专题一):从 animateTo 到 Lottie,一篇吃透全部

✨ 零基础学 ArkUI 动画(专题一):从 animateTo 到 Lottie,一篇吃透全部

博主说: 一个 App 有没有「高级感」,90% 取决于动画做得好不好。ArkUI 的动画系统非常强大------从最简单的属性变化过渡,到复杂的路径动画、粒子特效,全部原生支持。今天这篇专题我会用 7 个从易到难的实战案例,带你把 ArkUI 动画体系一次性摸透。


📱 为什么要学动画?

动画的作用 例子
✅ 提升用户体验 按钮点击有反馈,不「愣」
✅ 引导注意力 新功能入口闪烁提示
✅ 平滑状态切换 列表增删有过渡,不「闪跳」
✅ 传递品牌质感 加载动画彰显设计品味

⚙️ 运行环境

项目 要求
DevEco Studio 5.0.3.800+
HarmonyOS SDK API 12+
开发语言 ArkTS

🛠️ 7 个案例吃透 ArkUI 动画

🎯 案例 1:animateTo 显式动画 --- 最基础也最常用

场景: 点击按钮,让方块从左上角移动到右下角。

typescript 复制代码
@Entry
@Component
struct Demo1_AnimateTo {
  @State offsetX: number = 0;
  @State offsetY: number = 0;
  @State opacity: number = 1;
  @State rotate: number = 0;

  build() {
    Column() {
      Column()
        .width(80).height(80)
        .backgroundColor('#007AFF')
        .borderRadius(12)
        .opacity(this.opacity)
        .rotate({ angle: this.rotate })
        .offset({ x: this.offsetX, y: this.offsetY })
        .animation({ duration: 800, curve: Curve.EaseInOut }) // 属性动画

      Button('▶ 执行动画')
        .margin({ top: 40 })
        .onClick(() => {
          animateTo({ duration: 1000, curve: Curve.ElasticInOut }, () => {
            this.offsetX = 150;
            this.offsetY = 100;
            this.opacity = 0.5;
            this.rotate = 360;
          });
        })

      Button('↺ 重置')
        .margin({ top: 12 })
        .backgroundColor('#E5E5EA').fontColor('#333')
        .onClick(() => {
          animateTo({ duration: 500 }, () => {
            this.offsetX = 0;
            this.offsetY = 0;
            this.opacity = 1;
            this.rotate = 0;
          });
        })
    }
    .width('100%').height(300)
  }
}

核心解析:

复制代码
animateTo({ duration, curve, delay }, () => {
  // 在这个闭包中修改 @State 变量
  // 框架自动捕捉变化并生成动画
})
参数 说明 常用值
duration 时长(毫秒) 300~1000
curve 缓动曲线 Curve.EaseInOut / ElasticInOut / Bounce
delay 延迟(毫秒) 0~500
iterations 播放次数 -1 = 无限循环

运行结果:方块从左上角平滑移动到右下角,同时旋转 360° + 透明度减半。


🎯 案例 2:.animation() 属性动画 --- 链式调用的优雅写法

场景: 鼠标悬停(或点击)时按钮放大 + 变色。

typescript 复制代码
@Component
struct Demo2_PropertyAnimation {
  @State scale: number = 1;
  @State bgColor: Color = Color.Blue;

  build() {
    Button('悬浮放大变色')
      .width(200).height(56)
      .backgroundColor(this.bgColor)
      .scale({ x: this.scale, y: this.scale })
      .animation({
        duration: 400,
        curve: Curve.FastOutSlowIn,
        iterations: -1,        // 无限循环
        playMode: PlayMode.Alternate // 往返
      })
      .onClick(() => {
        // 仅需修改状态,animation 自动处理过渡
        this.scale = this.scale === 1 ? 1.2 : 1;
        this.bgColor = this.bgColor === Color.Blue ? Color.Red : Color.Blue;
      })
  }
}

区别对比:

方式 写法 适用场景
animateTo() 函数调用包裹状态修改 一次性过渡、复杂的多属性联动
.animation() 链式调用挂在组件上 持续性的交互动画、循环动画

🎯 案例 3:transition 过渡动画 --- 组件的「入场/离场」

场景: 点击添加卡片,新卡片优雅滑入;点击删除,卡片淡出。

typescript 复制代码
@Entry
@Component
struct Demo3_Transition {
  @State items: number[] = [1, 2, 3];
  private nextId: number = 4;

  build() {
    Column() {
      Button('➕ 添加卡片')
        .onClick(() => {
          animateTo({ duration: 300 }, () => {
            this.items.unshift(this.nextId++);
          });
        })

      List({ space: 8 }) {
        ForEach(this.items, (item: number) => {
          ListItem() {
            Row() {
              Text(`卡片 #${item}`).fontSize(18)
              Button('✕').fontColor('#FF3B30')
                .backgroundColor('transparent')
                .onClick(() => {
                  animateTo({ duration: 300 }, () => {
                    const idx = this.items.indexOf(item);
                    if (idx > -1) this.items.splice(idx, 1);
                  });
                })
            }
            .padding(20)
            .backgroundColor('#F0F4FF')
            .borderRadius(12)
            .width('90%')
          }
          // 过渡动画:入场从右侧滑入 + 淡入
          .transition(
            TransitionEffect.translate({ x: 200 })
              .combine(TransitionEffect.opacity(0))
              .animation({ duration: 400, curve: Curve.EaseOut })
          )
        }, (item: number) => item.toString())
      }
      .width('100%')
    }
  }
}

TransitionEffect 可选效果:

效果 用途
TransitionEffect.opacity(0) 淡入淡出
TransitionEffect.translate({ x: 200 }) 从右侧滑入
TransitionEffect.scale({ x: 0, y: 0 }) 缩放出现
TransitionEffect.rotate({ angle: 90 }) 旋转进入
.combine() 组合多种效果

🎯 案例 4:关键帧动画 --- 多阶段连续动画

场景: 加载图标------旋转 + 缩放 + 变色三段循环。

typescript 复制代码
@Entry
@Component
struct Demo4_Keyframe {
  @State angle: number = 0;
  @State scale: number = 1;
  @State color: Color = Color.Blue;

  build() {
    Column() {
      Circle()
        .width(60).height(60)
        .fill(this.color)
        .scale({ x: this.scale, y: this.scale })
        .rotate({ angle: this.angle })

      Button('▶ 播放关键帧动画')
        .margin({ top: 20 })
        .onClick(() => {
          // 第一阶段:旋转 360°
          animateTo({ duration: 600, curve: Curve.EaseIn }, () => {
            this.angle = 360;
            this.color = Color.Orange;
          });
          // 第二阶段:放大 + 变色(等第一阶段结束)
          setTimeout(() => {
            animateTo({ duration: 400, curve: Curve.EaseOut }, () => {
              this.scale = 1.5;
              this.color = Color.Red;
            });
          }, 600);
          // 第三阶段:恢复原状
          setTimeout(() => {
            animateTo({ duration: 300 }, () => {
              this.angle = 0;
              this.scale = 1;
              this.color = Color.Blue;
            });
          }, 1000);
        })
    }
    .width('100%').height(200)
  }
}

🎯 案例 5:几何形变动效 --- 不同组件间的平滑过渡

场景: 列表卡片点击展开为详情页。

typescript 复制代码
@Entry
@Component
struct Demo5_GeometryTransition {
  @State expanded: boolean = false;
  @State private id: string = 'sharedCard';

  build() {
    Column() {
      if (!this.expanded) {
        // 缩略卡片
        Row() {
          Circle().width(40).height(40).fill('#007AFF')
          Column() {
            Text('点击展开').fontSize(16).fontWeight(FontWeight.Bold)
            Text('查看详细信息...').fontSize(13).fontColor('#999')
          }.margin({ left: 12 })
        }
        .padding(16)
        .width('90%')
        .backgroundColor('#F0F4FF')
        .borderRadius(12)
        .geometryTransition(this.id)  // 共享同一个 id
        .onClick(() => {
          animateTo({ duration: 400 }, () => { this.expanded = true; });
        })
      } else {
        // 详情卡片
        Column() {
          Circle().width(80).height(80).fill('#007AFF')
          Text('详细信息').fontSize(22).fontWeight(FontWeight.Bold)
            .margin({ top: 12 })
          Text('这里是点击展开后的详情内容,篇幅可以很长...')
            .fontSize(15).fontColor('#555')
            .margin({ top: 8 }).lineHeight(24)
          Button('收起').margin({ top: 20 })
            .onClick(() => {
              animateTo({ duration: 400 }, () => { this.expanded = false; });
            })
        }
        .padding(24)
        .width('90%')
        .backgroundColor('#F0F4FF')
        .borderRadius(16)
        .geometryTransition(this.id)
      }
    }
    .width('100%').height('100%')
    .padding(16)
  }
}

🎯 案例 6:手势驱动动画 --- 拖拽 + 弹簧效果

场景: 拖拽卡片,松手后弹簧回弹。

typescript 复制代码
@Entry
@Component
struct Demo6_GestureAnimation {
  @State offsetX: number = 0;
  @State offsetY: number = 0;

  build() {
    Column() {
      Column()
        .width(100).height(100)
        .backgroundColor('#FF9500')
        .borderRadius(16)
        .offset({ x: this.offsetX, y: this.offsetY })
        .gesture(
          PanGesture({ distance: 10 })
            .onActionStart(() => {
              // 拖拽开始:取消正在执行的动画
            })
            .onActionUpdate((event: GestureEvent) => {
              this.offsetX = event.offsetX;
              this.offsetY = event.offsetY;
            })
            .onActionEnd(() => {
              // 松手后弹簧回弹
              animateTo({
                duration: 600,
                curve: Curve.SpringMotion  // 弹簧曲线
              }, () => {
                this.offsetX = 0;
                this.offsetY = 0;
              });
            })
        )
        .shadow({ radius: 10, color: '#40000000', offsetY: 4 })

      Text('拖拽我,松手回弹').fontSize(14).fontColor('#999').margin({ top: 12 })
    }
    .width('100%').height(400)
    .justifyContent(FlexAlign.Center)
  }
}

🎯 案例 7:完整实战 --- 点赞爱心动画

场景: Instagram 风格的点赞动画------点击爱心,图标放大 + 变色 + 粒子飞散。

typescript 复制代码
@Entry
@Component
struct Demo7_LikeAnimation {
  @State liked: boolean = false;
  @State scale: number = 1;
  @State particles: { id: number; x: number; y: number }[] = [];

  build() {
    Column() {
      Text(this.liked ? '❤️' : '🤍')
        .fontSize(64)
        .scale({ x: this.scale, y: this.scale })
        .onClick(() => {
          this.liked = !this.liked;
          // 点赞动画:先放大再回弹
          animateTo({ duration: 200, curve: Curve.EaseOut }, () => {
            this.scale = 1.4;
          });
          setTimeout(() => {
            animateTo({ duration: 300, curve: Curve.SpringMotion }, () => {
              this.scale = 1.0;
            });
          }, 200);

          // 粒子效果(用 Stack 叠加多个小圆点飞散)
          if (this.liked) {
            for (let i = 0; i < 6; i++) {
              const angle = (i / 6) * 360;
              setTimeout(() => {
                this.particles.push({
                  id: Date.now() + i,
                  x: Math.cos(angle * Math.PI / 180) * 60,
                  y: Math.sin(angle * Math.PI / 180) * 60
                });
                // 1 秒后移除粒子
                setTimeout(() => { this.particles.shift(); }, 1000);
              }, i * 50);
            }
          }
        })

      // 粒子渲染
      Stack() {
        ForEach(this.particles, (p) => {
          Circle().width(8).height(8).fill('#FF3B30')
            .position({ x: 32 + p.x, y: 32 + p.y })
            .opacity(0.6)
        }, (p) => p.id.toString())
      }
      .width(100).height(100)
      // 把 Stack 定位到心形上方
      .position({ x: '50%', y: '50%' })
      .translate({ x: -50, y: -50 })

      Text('点击爱心点赞').fontSize(14).fontColor('#999').margin({ top: 40 })
    }
    .width('100%').height(300)
    .justifyContent(FlexAlign.Center)
  }
}

📊 ArkUI 动画体系总览

复制代码
ArkUI 动画
├── 显式动画 animateTo()
│   ├── 基础过渡
│   ├── 多属性联动
│   └── 弹簧效果 (SpringMotion)
├── 属性动画 .animation()
│   ├── 持续循环
│   └── 往返 (Alternate)
├── 过渡动画 TransitionEffect
│   ├── 入场 (opacity/translate/scale/rotate)
│   └── 离场 (同上)
├── 几何形变 geometryTransition
│   └── 组件 A ↔ 组件 B 共享 ID
├── 关键帧动画(链式 animateTo)
│   └── 多阶段连续动画
├── 路径动画 motionPath
│   └── 沿贝塞尔曲线运动
└── 手势驱动动画
    └── PanGesture + 弹簧回弹

⚠️ 避坑指南

原因 正确做法
动画不执行 修改的不是 @State 变量 确保变量有 @State 装饰
动画闪烁 animateTo 中改了太多状态 拆分多个 animateTo 调用
transition 无效 忘了给组件设置 transition 属性 .transition(TransitionEffect.opacity(0))
geometryTransition 不联动 两个组件的 id 不一致 确保共享同一个 id 字符串
循环动画卡顿 .animation({ iterations: -1 }) 滥用 减少动画组件数量,用 Canvas 替代
弹簧效果没弹性 没用 Curve.SpringMotion 普通 Curve 没有回弹效果

🔥 最佳实践

  1. 动画时长黄金法则:UI 反馈 ≤ 200ms,过渡动画 300~400ms,强调动画 600~1000ms
  2. 缓动曲线选择 :入门用 EaseInOut,进阶用 FastOutSlowIn(Material Design 标准)
  3. 少即是多:不要每个交互都加动画------只动画化「用户会注意到的变化」
  4. 配合手势:拖拽 + 弹簧回弹效果让 App 有「物理感」
  5. 性能监控:复杂动画用 DevEco Studio 的 Profile 工具检测帧率(目标 60fps)
  6. 降级方案 :低端设备自动关闭动画(通过 @ohos.settings 读取设备等级)

📄 👉 点此查看动画专题截图网页


官方文档: HarmonyOS 应用开发文档

相关推荐
李二。1 小时前
HarmonyOS NEXT 屏幕取色器设计与实现详解
华为·harmonyos
●VON1 小时前
AtomGit Flutter鸿蒙客户端:Provider状态管理
flutter·华为·跨平台·harmonyos·鸿蒙
伶俜661 小时前
# [特殊字符] 零基础学 ArkUI 数据持久化(专题三):5 种存储方案深度对比
学习·华为·wpf·harmonyos
FrameNotWork1 小时前
HarmonyOS6.1 图像分类应用完整实战:从模型到界面
人工智能·分类·数据挖掘·harmonyos
带刺的坐椅2 小时前
SolonCode(编码智能体)支持鸿蒙 PC
java·web·ai编程·harmonyos·soloncode·鸿蒙 pc
Lucky_ldy2 小时前
51单片机的学习终(结合中科协的个人自用笔记)
笔记·学习·51单片机
李二。2 小时前
HarmonyOS NEXT 定时关机工具:从设计到实现的完整技术解析
华为·harmonyos
星幻元宇VR2 小时前
消防教育基地展厅设备【消防知识安全竞赛系统】
人工智能·科技·学习·安全