上个月,我们的电商应用做了一波视觉升级。产品经理拿着某知名 App 的录屏跑过来:"你看人家这个商品详情,点开图片的时候,那张图是'飞'过去的,特别丝滑。咱们现在这个'啪'一下直接切过去,太生硬了。"
这需求听着简单,不就是加个转场动画嘛。但在 ArkUI 里,稍微复杂的动效,如果和页面生命周期没对齐,那简直是一场灾难。我一开始兴冲冲地用了 pageTransition 全局转场,结果动画还没播完,弹窗一闪,直接把我整个路由栈干碎了。
这篇复盘,记录我是怎么从一个只会用 animateTo 的菜鸟,一步步填平高阶动效那些反人类设计的坑。
从最简单的 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 的质感,但这背后是对生命周期和渲染层级极其严苛的考验。
-
不要迷信动画 API,物理曲线(摩擦、弹性)才是流畅感的关键。
-
geometryTransition的 ID 必须唯一且稳定,别用 index 当 ID。 -
永远不要在页面转场动画未结束时,触发弹窗或异步网络请求。