做 HarmonyOS6 PC 端开发的时候,动画这个东西真的是"一看就会,一做就废"。

很多开发者(包括我自己)写动画的时候最常干的事情就是:duration 给个 300,curve 用个默认的,然后就不管了。直到有一天产品经理过来说"这个动画怎么这么生硬",才意识到——动画曲线才是灵魂

今天咱们就把 HarmonyOS ArkUI 里的 Curve 枚举彻底搞明白。不搞虚的,直接上对比,让你看完就知道什么场景用什么曲线。

先搞清楚 animateTo() 这个全局函数

HarmonyOS 的 ArkUI 框架提供了一个全局动画函数 animateTo(),它的签名很简单:

animateTo(
  value: AnimateParam,
  closure: () => void
)

AnimateParam 里面有几个关键参数:

参数 类型 说明
duration number 动画时长,单位毫秒
curve Curve | string 动画曲线,也就是今天的主角
delay number 延迟执行时间
iterations number 重复次数
onFinish () => void 动画结束回调

closure 闭包里面放的就是你要改变的状态变量。ArkUI 会自动检测闭包里哪些 @State 变量发生了变化,然后对使用了这些变量的 UI 属性做动画插值。

这个设计坦白讲非常优雅——你只需要告诉框架"从 A 变到 B",中间的过渡帧它帮你算。

Curve 枚举全家福

ArkUI 内置了这些 Curve 值:

enum Curve {
  Linear,           // 匀速
  Ease,             // 默认缓动,类似 EaseInOut
  EaseIn,           // 慢入快出
  EaseOut,          // 快入慢出
  EaseInOut,        // 慢入慢出(两头慢中间快)
  FastOutLinearIn,  // 快速启动,匀速结束
  FastOutSlowIn,    // 快速启动,缓慢结束
  LinearOutSlowIn,  // 匀速启动,缓慢结束
  Sharp,            // 锐利曲线
  Rhythm,           // 节奏曲线
  Smooth,           // 平滑曲线
  Friction,         // 摩擦力曲线(模拟物理阻尼)
}

一共12种。说实话,日常开发用到的也就五六种,但搞清楚每一种的特性,关键时刻能救命。

实测对比:5种常用曲线

下面这段代码做了一个非常直观的对比实验——5个进度条,分别使用 Linear、EaseIn、EaseOut、EaseInOut、FastOutLinearIn 五种曲线,同时从 0% 跑到 90%,时长统一 1500ms。

@Entry
@Component
struct CurveCompareDemo {
  @State positions: number[] = [0, 0, 0, 0, 0]
  @State labels: string[] = ['Linear', 'EaseIn', 'EaseOut', 'EaseInOut', 'FastOutLinearIn']

  build() {
    Column() {
      Text('曲线动画对比')
        .fontSize(18).fontWeight(FontWeight.Bold).margin({ bottom: 8 })

      Column() {
        ForEach([0, 1, 2, 3, 4], (idx: number) => {
          Row() {
            Text(this.labels[idx])
              .width(70).fontSize(11).fontColor('#999999')
            Stack({ alignContent: Alignment.Start }) {
              Column()
                .width('100%').height(8)
                .backgroundColor('#E0E0E0')
                .borderRadius(4)
              Column()
                .width(`${this.positions[idx]}%`).height(8)
                .backgroundColor(this.getColor(idx))
                .borderRadius(4)
                .animation({ duration: 1500 })
            }
            .layoutWeight(1)
          }
          .width('100%').height(36).alignItems(VerticalAlign.Center)
        })

        Row({ space: 10 }) {
          Button('启动对比')
            .onClick(() => {
              this.startAnimation(0, Curve.Linear)
              this.startAnimation(1, Curve.EaseIn)
              this.startAnimation(2, Curve.EaseOut)
              this.startAnimation(3, Curve.EaseInOut)
              this.startAnimation(4, Curve.FastOutLinearIn)
            })
          Button('重置')
            .onClick(() => {
              for (let i = 0; i < 5; i++) { this.positions[i] = 0 }
            })
        }
        .width('100%').justifyContent(FlexAlign.SpaceEvenly).margin({ top: 16 })
      }
      .width('100%').backgroundColor('#FFFFFF').borderRadius(12).padding(16)
    }
    .width('100%').height('100%').backgroundColor('#F5F6FA').padding(16)
  }

  getColor(index: number): string {
    const colors = ['#FF6B6B', '#FFA500', '#FFD93D', '#6BCB77', '#4ECDC4']
    return colors[index]
  }

  startAnimation(index: number, curve: Curve) {
    animateTo({ duration: 1500, curve: curve }, () => {
      this.positions[index] = 90
    })
  }
}

运行效果描述

点击"启动对比"后,5个进度条同时开始跑:

  • Linear:匀速前进,速度恒定,看着很"机械"
  • EaseIn:起步特别慢,后半段加速冲上去
  • EaseOut:起步很快,快到终点时慢慢减速
  • EaseInOut:起步慢 → 中间加速 → 到达时减速,最自然的运动感
  • FastOutLinearIn:嗖的一下冲出去,然后匀速到达

这个对比实验虽然简单,但效果非常直观。你会发现 1500ms 的时长足够长,每条曲线的速度差异肉眼可辨。

每种曲线的数学特性和适用场景

Linear(线性)

数学上就是一条直线 y = x,没有加减速。

适用场景:进度条、倒计时、持续旋转动画。这类场景本身就不需要加减速,匀速反而更准确。

不适用场景:几乎所有位移类动画。一个按钮从左边飞到右边用 Linear 曲线,看着就像机器人推过去的。

EaseIn(缓入)

曲线特征是 y = x^n(n > 1),起点斜率接近 0,终点斜率最大。

适用场景:元素离场/退出。比如一个卡片飞出屏幕,用 EaseIn 会让它看起来像"蓄力然后加速离开",符合物理直觉。

踩坑经验:别用 EaseIn 做入场动画。一个元素从屏幕外飞进来,如果用 EaseIn,它会在屏幕边缘磨蹭半天,用户等着急。

EaseOut(缓出)

跟 EaseIn 反过来,起点斜率最大,终点逐渐减速至 0。

适用场景:元素入场。从屏幕外飞入的弹窗、滑入的抽屉菜单、展开的下拉面板——用 EaseOut 就对了。它给人一种"物体因为摩擦力而逐渐停下来"的感觉。

坦白讲,如果你不知道用什么曲线,EaseOut 是最安全的选择。大多数 Material Design 和 Apple HIG 推荐的交互动画默认就是 EaseOut 类型的曲线。

EaseInOut(缓入缓出)

两头慢中间快,像一个钟摆运动。

适用场景:状态切换类动画。比如开关按钮的滑动、Tab 切换的位移、展开/折叠面板。这类动画没有明确的"入场"和"离场"之分,用 EaseInOut 最自然。

在 HarmonyOS6 PC 端,很多系统级动画就是 EaseInOut 曲线,比如窗口最小化/恢复。保持跟系统一致的动画风格,用户体验会更统一。

FastOutLinearIn(快出匀入)

起步很快,后半段匀速。这是个比较特殊的曲线。

适用场景:需要快速响应的位移动画,比如快速展开的菜单、工具栏的出现。它比 EaseOut 更"干脆",没有明显的减速过程。

不太推荐用在需要优雅感的场景中,因为匀速结束的部分会显得有点"突然就停了"。

另外几种曲线简评

上面5种是最常用的,剩下几种也简单聊聊:

FastOutSlowIn

快速启动 + 缓慢结束。跟 EaseOut 类似但减速段更长,适合需要"慢慢到位"的场景,比如大尺寸面板的展开。在 HarmonyOS6 PC 端的大屏幕上,面板展开动画用这个曲线特别合适,因为大屏用户对动画的"从容感"要求更高。

LinearOutSlowIn

匀速启动 + 缓慢结束。起步不会很突兀,结束很柔和。适合那些"不要太快但也不能太慢"的微妙场景。

Sharp

锐利曲线,过渡非常快。适合需要"一闪而过"效果的场景,比如消息气泡的消失。

Friction

模拟摩擦力阻尼的效果,会有轻微的回弹感。非常适合拖拽释放后的归位动画——你拖一个卡片松手后它弹回原位,用 Friction 曲线特别真实。

Smooth 和 Rhythm

这两个比较偏"感觉型",Smooth 更丝滑,Rhythm 有节奏感。实际项目中用的不多,但在一些品牌展示类页面中可以考虑。

animateTo() vs .animation() 到底用哪个?

这是很多 HarmonyOS6 PC 开发者会纠结的问题。上面的对比 Demo 里其实两种写法都有:

// 方式一:animateTo() 全局函数
animateTo({ duration: 1500, curve: Curve.Linear }, () => {
  this.positions[0] = 90
})

// 方式二:.animation() 属性修饰器
Column()
  .width(`${this.positions[idx]}%`)
  .animation({ duration: 1500 })

区别在哪?

animateTo() 是命令式的——你在某个事件回调里主动调用,可以精确控制动画的触发时机、曲线、延迟,还能拿到 onFinish 回调。适合"点击按钮后执行一组动画"这种场景。

.animation() 是声明式的——你把它挂在组件上,当绑定的状态变量发生变化时自动触发动画。它更"懒",但也更简洁。适合"状态变了就自动过渡"的场景。

实际项目里我建议混合使用。比如上面的对比 Demo,进度条用了 .animation() 修饰器(因为宽度变化只需要简单的过渡),而触发动画用了 animateTo()(因为需要指定不同的曲线)。

有个细节:当两者同时存在时,animateTo() 的 curve 参数会覆盖 .animation() 的 curve。所以你在 .animation() 上设的 curve,可能会在 animateTo() 调用时失效。这个坑我踩过,记录一下。

PC 端动画的特殊考量

做 HarmonyOS6 PC 端开发和手机端有个很大的区别:屏幕大,动画的位移距离更长

一个卡片从屏幕左侧飞到右侧,在手机上可能就 300px 的距离,在 PC 端可能是 800px 甚至更多。同样的 duration 和 curve,在不同距离下的视觉感受完全不同。

我的经验法则:

  1. 短距离位移(< 100px):300-400ms,EaseOut
  2. 中距离位移(100-300px):400-600ms,EaseOut 或 EaseInOut
  3. 长距离位移(> 300px):600-800ms,EaseInOut 或 FastOutSlowIn
  4. 缩放/透明度变化:不受距离影响,保持 200-400ms 即可

PC 端用户对动画的容忍度比手机端高一些(可能因为大屏本身就有"大气"的感觉),但动画太慢一样会被吐槽。建议在真实设备上反复调试,别在模拟器上看着差不多就完事了。

一个实用的曲线选择决策树

最后分享一个我自己用的决策流程:

要做动画 → 是什么类型?
├── 进度/持续旋转 → Linear
├── 元素入场(从外到内)→ EaseOut
├── 元素离场(从内到外)→ EaseIn
├── 状态切换/原地变形 → EaseInOut
├── 需要快速响应 → FastOutLinearIn 或 FastOutSlowIn
├── 拖拽回弹 → Friction
└── 不确定 → EaseOut(万能选择)

保存这个决策树,下次写动画的时候就不用纠结了。

小结

动画曲线这个东西,说到底是让 UI 的变化"像真实世界里的运动"。真实世界里,物体不会瞬间启动也不会瞬间停止——它们有加速和减速的过程。Curve 枚举就是在模拟这个过程。

把上面那个对比 Demo 跑一遍,盯着 5 个进度条看十几遍,你对曲线的理解会比看任何文档都深刻。

Logo

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

更多推荐