被弹窗搞崩的转场动画,HarmonyOS 7 高阶动效与共享元素

上个月,我们的电商应用做了一波视觉升级。产品经理拿着某知名 App 的录屏跑过来:"你看人家这个商品详情,点开图片的时候,那张图是'飞'过去的,特别丝滑。咱们现在这个'啪'一下直接切过去,太生硬了。"

这需求听着简单,不就是加个转场动画嘛。但在 ArkUI 里,稍微复杂的动效,如果和页面生命周期没对齐,那简直是一场灾难。我一开始兴冲冲地用了 pageTransition 全局转场,结果动画还没播完,弹窗一闪,直接把我整个路由栈干碎了。

这篇复盘,记录我是怎么从一个只会用 animateTo 的菜鸟,一步步填平高阶动效那些反人类设计的坑。

https://placeholder-01.jpg/

从最简单的 animateTo 开始

我先试着优化了最基础的点击缩放效果。之前为了图省事,直接写了这样的代码:

typescript

复制代码
// 错误示范:生硬的属性切换
Button('点赞')
  .scale({ x: this.isLiked ? 1.2 : 1, y: this.isLiked ? 1.2 : 1 })
  .onClick(() => {
    this.isLiked = !this.isLiked;
  })

点一下,瞬间放大,点一下,瞬间缩小。没有任何过渡,看着非常廉价。

在 ArkUI 里,要让属性变化带过渡,必须把它包进 animateTo 里。但这里有个非常关键的细节:曲线的选择。

很多新手直接用默认的 Curve.Linear(匀速),这玩意儿在物理世界里是不存在的。真实的物体运动一定有加速度和惯性。重构后我把曲线换成了 Curve.Friction(摩擦力曲线),手感瞬间就出来了。

typescript

复制代码
.onClick(() => {
  animateTo({
    duration: 300,
    curve: Curve.Friction, // 带有物理阻尼感
    iterations: 1,
    playMode: PlayMode.Normal
  }, () => {
    this.isLiked = !this.isLiked;
  })
})

geometryTransition 共享元素

图片"飞过去"的效果,靠的是 geometryTransition。

思路看起来极其简单:给列表页的缩略图,和详情页的大图,绑上同一个 ID。系统会自动计算它们的位置差,帮你做补间动画。

typescript

复制代码
// 列表页
Image(this.item.imageUrl)
  .geometryTransition(`img_${this.item.id}`) 
  .onClick(() => {
    router.pushUrl({ url: 'pages/Detail', params: { id: this.item.id } });
  })

// 详情页
Image(this.imageUrl)
  .geometryTransition(`img_${this.imageId}`)

一跑起来,效果确实出来了,但伴随而来的是严重的画面撕裂。图片在飞行的过程中,突然闪了一下,而且原本的圆角在飞行途中变成了直角。

深坑解析: geometryTransition 其实就是个障眼法。它在转场期间,会在最顶层(Overlay)创建一个浮层来绘制这个共享元素。如果你在组件上加了 .borderRadius() 或者 .clip(),这个浮层如果没继承这些属性,动画就会穿帮。

为了解决圆角闪烁,我必须在两个页面的 Image 上,严格保证 borderRadius、objectFit 等属性的绝对一致,并且在动画结束后立刻取消浮层,让真实的组件接管渲染。

转场与弹窗的冲突

最要命的问题来了。在详情页 aboutToAppear 里,如果用户的登录态失效了,我会弹出一个 CustomDialog 强制他登录。

结果就是:图片还在天上飞呢,登录弹窗就在屏幕中央弹出来了,挡住了所有视线。更糟的是,如果用户在动画没结束时就点击登录,路由跳转的瞬间,应用直接卡死闪退。

底层原因: ArkUI 的页面转场动画是异步执行的,通常需要 300ms。而你在页面的生命周期里触发弹窗,它会立刻抢占 UI 线程的焦点,导致动画渲染进程和事件响应进程打架。

解法: 延迟弹窗,或者把动画和业务逻辑彻底剥离。

typescript

复制代码
onPageShow() {
  // 监听转场动画结束事件,或者自己用定时器兜底
  setTimeout(() => {
    this.checkLoginStatusAndShowDialog();
  }, 350); // 等待转场动画完全结束
}

高阶动效确实能提升 App 的质感,但这背后是对生命周期和渲染层级极其严苛的考验。

  1. 不要迷信动画 API,物理曲线(摩擦、弹性)才是流畅感的关键。

  2. geometryTransition 的 ID 必须唯一且稳定,别用 index 当 ID。

  3. 永远不要在页面转场动画未结束时,触发弹窗或异步网络请求。