为什么别人的应用那么丝滑,我的像 PPT 翻页?

现在做移动端应用,功能谁都能写出来,但体验好不好,差别就在细节上。尤其是动画。

很多同学写出来的应用,点击按钮,页面“啪”地一下突然出现,或者图片“唰”地一下突然变大,生硬得像个 PPT。

你可能说了:“我加了 animateTo 了啊!” 但你加的动画可能只是让透明度变了变,或者位移了一下,缺乏连贯性和物理感。

HarmonyOS 7 在动效方面提供了非常强大的工具,尤其是共享元素转场(GeometryTransition),能做出媲美原生 iOS 或 Material Design 的高级视觉效果。这篇就带你搞懂这些高级动效,代码都是开箱即用的。

https://placeholder-01.jpg/

一、属性动画:animateTo 的正确打开方式

最基础的动画就是属性动画。ArkUI 提供了一个极其好用的 animateTo 函数。

很多同学写动画的姿势是这样的:

typescript

Button('点击')
  .onClick(() => {
    this.isExpanded = !this.isExpanded;
  })
  .scale({ x: this.isExpanded ? 2 : 1, y: this.isExpanded ? 2 : 1 })

这样没有任何动画过渡,状态一变,UI 瞬间变化。

正确写法是:把状态变化包在 animateTo 的闭包里。

typescript

Button('点击')
  .onClick(() => {
    animateTo({
      duration: 300,
      curve: Curve.Friction, // 推荐用带有物理感的曲线
      iterations: 1,
      playMode: PlayMode.Normal
    }, () => {
      // 在这里修改状态变量,ArkUI 会自动为变化做平滑过渡
      this.isExpanded = !this.isExpanded;
    });
  })
  .scale({ x: this.isExpanded ? 2 : 1, y: this.isExpanded ? 2 : 1 })

这里推荐用 Curve.Friction 或者 Curve.ExtremeDeceleration。默认的 Curve.Linear 看起来非常机械,带点摩擦力的曲线会更贴近真实物理世界。

二、共享元素转场:让元素“飞”过去

这是本篇的重头戏。

想象一个场景:你有一个列表,每个列表项都有一张缩略图。点击缩略图,跳转到详情页,缩略图应该平滑地放大,变成详情页里的大图,而不是直接“切”过去。

这就是共享元素转场。在 HarmonyOS 里,靠 geometryTransition 实现。

核心思路是:给列表里的图和详情页里的图,设置同一个 geometryTransition 的 ID。

typescript

// 列表页
Image(this.item.imageUrl)
  .width(100)
  .height(100)
  .geometryTransition('image_' + this.item.id, { follow: true })
  .onClick(() => {
    router.pushUrl({ url: 'pages/DetailPage', params: { imageId: this.item.id } });
  })

typescript

// 详情页
Image(this.imageUrl)
  .width('100%')
  .height(400)
  .geometryTransition('image_' + this.imageId, { follow: true })

这两个组件,只要 ID 相同,并且存在于同一个页面转场过程中,系统就会自动计算它们的起始位置和尺寸,在转场时绘制一个平滑的缩放动画。

坑来了:geometryTransition 的 ID 必须唯一且匹配!

如果你列表里有 10 个图,全用了 'image' 这个 ID,那么点击任何一个,都会飞到同一个位置,画面直接乱套。务必用 'image_' + id 这种格式拼接。

另外,follow: true 这个参数很关键。它表示在动画过程中,元素是否会跟随手指或者转场布局变化。如果不加,转场效果可能不够自然。

https://placeholder-02.png/

三、自定义转场:打破默认的“切页面”

有时候,你不希望页面是默认的左右滑动或者上下滑动。你想做一个类似“卡片翻转”或者“淡入淡出”的效果。

这时候就要用到 pageTransition 函数。

typescript

// 在页面的根组件上使用
@Entry
@Component
struct DetailPage {
  build() {
    Column() {
      // ...
    }
    .width('100%')
    .height('100%')
    .pageTransition({
      type: RouteType.Push, // 针对 push 动作
      duration: 400,
      curve: Curve.FastOutSlowIn,
      translate: {
        x: { from: 1, to: 0 }, // 从右侧 100% 滑入
        y: { from: 0, to: 0 }
      },
      opacity: { from: 0, to: 1 } // 淡入
    })
  }
}

你可以自由组合 translate、scale、opacity、rotate。通过调整这些参数,你能做出“从底部弹出”、“翻转进入”、“缩放淡入”等各种效果。

四、动效避坑:性能与体验的平衡

动画虽然酷,但也是一把双刃剑。用得不好,卡顿和发热分分钟找上门。

坑一:在动画里改变布局(Layout)属性。

如果你用 animateTo 去改变 width、height、margin 这些布局属性,会触发 ArkUI 重新排版(Layout),性能开销极大。尽量只改 scale、translate、opacity、rotate 这些不触发布局变化的渲染属性。

坑二:动画导致内存泄漏。

如果你在 animateTo 的闭包里引用了 this,并且动画还没结束,页面就 aboutToDisappear 了,可能会出问题。确保动画逻辑在页面销毁前结束,或者使用 onFinish 回调清理资源。

坑三:geometryTransition 在跨页面时失效。

如果你的列表页和详情页不在同一个 Navigation 栈里,或者使用了不同的 pageTransition 配置,geometryTransition 可能会失效。保证它们在同一个路由栈里。

https://placeholder-03.png/

五、总结

高级动效不是炫技,而是为了让用户觉得“顺”。核心记住三点:

  1. animateTo 要配合物理曲线(如 Friction),别用默认的 Linear。

  2. geometryTransition 的 ID 必须唯一匹配,实现平滑的共享元素转场。

  3. 动画只改渲染属性(scale、translate、opacity),少改布局属性(width、height)。

Logo

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

更多推荐