ArkUI 动画系统属性动画、显式动画与路径动画

动画让界面"活"起来

静态界面能用,但缺了动画的过渡会显得生硬。ArkUI 提供了从简单到复杂的动画能力:属性动画、显式动画、路径动画,以及组件内置动画。这篇把常用动画类型讲清楚。

属性动画:给属性加动画

属性动画是最简单的动画方式——某个属性值变化时,自动生成过渡动画,而不是瞬间跳变。

@Entry
@Component
struct AttributeAnimationDemo {
  @State size: number = 100;

  build() {
    Column({ space: 20 }) {
      // 一个会变大变小的方块
      Text('')
        .width(this.size)
        .height(this.size)
        .backgroundColor('#FF6B35')
        .animation({  // 属性动画
          duration: 300,  // 时长 300ms
          curve: Curve.EaseInOut  // 缓动曲线
        })

      Button('变大')
        .onClick(() => {
          this.size = 200;  // 改属性,动画自动过渡
        })
      Button('变小')
        .onClick(() => {
          this.size = 100;
        })
    }
  }
}

.animation() 给这个组件的属性变化统一加动画。改 size 时,宽高不再是瞬间跳到 200,而是 300ms 内平滑过渡。

多属性组合动画

.animation() 一次只管一个组件的动画参数,但可以给多个属性加 animation,或指定动画哪些属性:

Text('')
  .width(this.size)
  .height(this.size)
  .backgroundColor(this.color)
  .borderRadius(this.radius)
  .animation({ duration: 300, curve: Curve.EaseInOut })  // 所有动画属性统一动画

上面 sizecolorradius 变化都会过渡。如果想对不同属性用不同动画,可以分别设置。

缓动曲线 Curve

缓动曲线决定了动画速度的变化方式,直接影响动画手感:

曲线特点适用
Curve.Linear匀速进度条、循环动画
Curve.EaseIn慢进快出元素消失、退出
Curve.EaseOut快进慢出元素出现、进入
Curve.EaseInOut慢进慢出大多数过渡
Curve.Friction摩擦减速模拟物理惯性

App 里的常规过渡用 EaseInOut,进入动画用 EaseOut(快进慢出更自然),退出用 EaseIn

显式动画:更精细的控制

属性动画是"改属性自动动",显式动画是"主动调 API 触发动画"。显式动画能控制更多细节:

@Entry
@Component
struct ExplicitAnimationDemo {
  @State width: number = 100;

  build() {
    Column({ space: 20 }) {
      Text('')
        .width(this.width)
        .height(100)
        .backgroundColor('#4A90E2')

      Button('显式动画展开')
        .onClick(() => {
          // 显式动画:包住要动画的状态修改
          this.getUIContext().animateTo({
            duration: 500,
            curve: Curve.EaseInOut,
            iterations: 1,  // 播放次数
            playMode: PlayMode.Normal
          }, () => {
            // 闭包里改状态,触发动画
            this.width = 300;
          });
        })
    }
  }
}

animateTo 的第一个参数是动画配置,第二个参数是回调,回调里的状态修改会被动画化。显式动画的优点:

  • 能控制 iterations(次数)、playMode(播放模式)
  • 能把多个状态修改放在一个闭包里,统一做动画

显式动画 vs 属性动画

属性动画:改属性 → 自动动(简单,适合固定动画)
显式动画:调 API → 闭包改状态 → 动画(灵活,适合复杂控制)

选型:
  普通 hover、尺寸变化 → 属性动画 .animation()
  需要控制次数/方向/多状态联动 → 显式动画 animateTo()

路径动画:沿轨迹运动

属性动画只能做值的变化(大小、颜色、透明度),路径动画让元素沿指定的轨迹运动:

@Entry
@Component
struct PathAnimationDemo {
  @State offsetX: number = 0;
  @State offsetY: number = 0;

  build() {
    Stack() {
      // 沿圆形轨迹运动的小球
      Circle()
        .width(30)
        .height(30)
        .fill('#FF6B35')
        .offset({ x: this.offsetX, y: this.offsetY })
        .animation({ duration: 2000, curve: Curve.Linear })
    }
    .onClick(() => {
      // 圆周运动:通过三角函数更新位置
      let angle = 0;
      let timer = setInterval(() => {
        angle += 0.1;
        this.offsetX = 150 * Math.cos(angle);  // x = r·cos(θ)
        this.offsetY = 150 * Math.sin(angle);  // y = r·sin(θ)
        if (angle >= Math.PI * 2) {
          clearInterval(timer);
        }
      }, 50);
    })
  }
}

路径动画本质是"按时间轴手动更新位置,配合属性动画做平滑过渡"。复杂路径(贝塞尔曲线、抛物线)都是这个思路——用数学公式算出每一帧的位置。

转场动画动画的完整案例

用属性动画 + 缓动曲线做一个"侧滑菜单":

@Entry
@Component
struct SlideMenuDemo {
  @State isOpen: boolean = false;
  @State menuOffset: number = -250;  // 菜单默认藏在左侧

  build() {
    Stack({ alignContent: Alignment.Start }) {
      // 主内容
      Column() {
        Text('主内容区')
        Button('打开菜单')
          .onClick(() => {
            this.isOpen = !this.isOpen;
            this.menuOffset = this.isOpen ? 0 : -250;
          })
      }
      .width('100%')
      .height('100%')

      // 侧滑菜单
      Column() {
        Text('菜单项 1')
        Text('菜单项 2')
        Text('菜单项 3')
      }
      .width(250)
      .height('100%')
      .backgroundColor('#333333')
      .offset({ x: this.menuOffset })
      .animation({ duration: 300, curve: Curve.EaseInOut })  // 平滑滑入滑出
    }
  }
}

菜单的 offset 在 -250 和 0 之间切换,.animation() 让它平滑滑动,300ms 的 EaseInOut 手感刚好。

动画的几条经验

  1. 时长别太长。界面过渡动画 200-400ms 合适,超过 500ms 会让人感觉"卡"。加载动画可以循环,但转场要快。

  2. 缓动曲线比时长更影响手感。同样的 300ms,Linear 生硬,EaseInOut 自然。默认 EaseInOut 是安全选择。

  3. 能属性动画就别显式动画。属性动画代码更简洁,只有需要控制次数、方向、多状态联动时才用 animateTo

  4. 避免动画里做重计算。动画回调(尤其是路径动画的 timer)里别做昂贵操作,否则掉帧。

  5. 列表项动画要节制。长列表里每项都做复杂动画会影响滚动性能,列表动画优先用组件内置的轻量动效。

Logo

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

更多推荐