
动画是 UI 的灵魂——没有动画的页面像幻灯片翻页有了动画才是活的交互。本文用五个独立 Demo 覆盖 ArkUI 中animateTo的四种核心变换和组合用法每个 Demo 自包含且可直接运行。一、我们要做什么一个动画演示页面包含五组独立动画缩放动画Scale— 元素放大回弹模拟点赞效果旋转动画Rotate— 图标 360° 旋转平移动画Translate— 卡片左右滑动透明度动画Opacity— 色块淡入淡出组合动画— scale rotate opacity 同时变化每个 Demo 由触发按钮 → 状态变化 →animateTo过渡驱动。二、animateTo 基础2.1 核心原理animateTo({duration:300,curve:Curve.EaseInOut},(){this.someStatenewValue;// 修改 State 变量});三要素duration时长— 毫秒300ms 是移动端动画的黄金时长curve曲线— 决定速度变化方式非匀速闭包中的状态修改— ArkUI 拦截闭包中的State变化用动画过渡到新值animateTo不是播放一段动画而是把状态变化的瞬间拉伸成平滑过渡。这和 Flutter 的AnimationController思路一致但 API 更简洁。2.2 动画曲线对比曲线感觉适用场景Curve.EaseIn先慢后快元素离开屏幕Curve.EaseOut先快后慢元素进入屏幕Curve.EaseInOut两端慢中间快状态切换最常用Curve.FastOutSlowIn快出慢进平移滑动Curve.Spring弹簧回弹点赞、心动效果选对曲线比选对时长更重要。错误的曲线会让动画感觉不对——即使只差 100ms。2.3 三件重要的事第一animateTo只对写在闭包中的State变化生效。// 正确 — myValue 在闭包中被修改animateTo({duration:300},(){this.myValue100;});// 错误 — myValue 在闭包外被修改animateTo 无效this.myValue100;animateTo({duration:300},(){// 空的什么都不会发生});第二父组件animateTo不会影响子组件内的状态。每个animateTo只作用于它闭包中直接修改的State。子组件的State是独立的。第三animateTo是异步的。调用animateTo后立即执行下一行代码动画在后台播放。如果需要动画完成后做某事用setTimeoutanimateTo({duration:300},(){this.value100;});setTimeout((){// 动画已结束},300);三、Demo 1缩放动画 — 回弹效果StatescaleValue:number1;privateanimateScale():void{// 第一步放大animateTo({duration:150,curve:Curve.EaseOut},(){this.scaleValue1.5;});// 第二步150ms 后缩回setTimeout((){animateTo({duration:200,curve:Curve.EaseIn},(){this.scaleValue1;});},150);}// 元素上应用Row().width(80).height(80).backgroundColor(AppColors.PRIMARY).borderRadius(40).scale({x:this.scaleValue,y:this.scaleValue})两步动画构成弹跳先放大到 1.5 倍150msEaseOut 先快后慢再缩回原大小200msEaseIn 先慢后快。为什么用两个animateTo而不是大 duration单个animateTo只能做单方向过渡——从 1 到 1.5 再回到 1不存在的animateTo只从当前值过渡到目标值。要实现去-回效果必须分两步。为什么第二步用setTimeout而不是直接写在后面animateTo不等动画播完就返回了。直接写在后面的代码立即执行会导致两个animateTo几乎同时触发——结果是从 1 直接跳到 1.5第一步还没播完就被第二步覆盖。setTimeout的延迟匹配第一步的 duration150ms确保第一步播完再播第二步。这个缩放模式可以直接用于点赞按钮心形图标放大回弹收藏按钮添加到购物车任何确认操作的即时反馈四、Demo 2旋转动画StaterotateAngle:number0;privateanimateRotate():void{consttargetthis.rotateAngle0?360:0;animateTo({duration:500,curve:Curve.EaseInOut},(){this.rotateAngletarget;});}// 元素上应用Image($r(sys.symbol.arrow_clockwise)).width(48).height(48).fillColor(AppColors.PRIMARY).rotate({angle:this.rotateAngle})每次点击在 0° 和 360° 之间切换。Curve.EaseInOut让旋转在两端慢、中间快——模拟物理世界的惯性。rotate的角度单位是度degrees不是弧度radians。360 一圈和 CSS 的transform: rotate(360deg)一致。可以扩展为刷新按钮的加载动画用setInterval连续增加角度展开/收起箭头的旋转设置齿轮的转动五、Demo 3平移动画StatetranslateX:number0;privateanimateTranslate():void{consttargetthis.translateX0?60:0;animateTo({duration:400,curve:Curve.FastOutSlowIn},(){this.translateXtarget;});}// 元素上应用Row().width(80).height(80).backgroundColor(#FAAD14).borderRadius(BorderRadius.MD).translate({x:this.translateX,y:0})元素在 X 轴方向来回滑动 60vp。Curve.FastOutSlowIn—— Material Design 标准曲线快出慢进完美适配平移。可以扩展为抽屉菜单的滑入滑出轮播卡片切换滑动删除确认元素向右滑出屏幕六、Demo 4透明度动画StateopacityValue:number1;privateanimateOpacity():void{consttargetthis.opacityValue1?0.15:1;animateTo({duration:500,curve:Curve.EaseInOut},(){this.opacityValuetarget;});}// 元素上应用Row().width(80%).height(60).backgroundColor(#722ED1).borderRadius(BorderRadius.MD).opacity(this.opacityValue)透明度在 1完全不透明和 0.15几乎透明之间切换。注意不是到 0——完全透明会让元素消失用户不知道为什么点不到。0.15 保留一丝可见性暗示它还在这里。可以扩展为模态遮罩层淡入淡出骨架屏切换为真实内容Toast 提示的出现消失关键细节.opacity(0.15)不是.opacity(0)。0.15 的可见度让用户知道元素没有消失只是半隐藏状态。如果到 0用户看不到回不来了——对复原操作造成认知负担。七、Demo 5组合动画StatecomboScale:number1;StatecomboAngle:number0;StatecomboOpacity:number1;privateanimateCombo():void{constshrinkthis.comboScale1;animateTo({duration:500,curve:Curve.EaseInOut},(){this.comboScaleshrink?1.3:1;this.comboAngleshrink?180:0;this.comboOpacityshrink?0.4:1;});}// 元素上应用Text(★).fontSize(48).fontColor(#FAAD14).scale({x:this.comboScale,y:this.comboScale}).rotate({angle:this.comboAngle}).opacity(this.comboOpacity)三个属性在同一个animateTo闭包中同时修改——ArkUI 自动同步三者的过渡不会出现缩放完了旋转还没开始的错位。一个animateTo闭包中修改多个State→ 所有过渡同步进行。这是实现复杂动画的正确方式——把相关的变化放在同一个闭包中框架保证同步。八、动画性能注意事项8.1 只有 transform 动画是 GPU 加速的scale、rotate、translate、opacity在 GPU 层执行不触发重新布局。这意味着60fps 流畅即使在低端设备上不影响列表滚动性能不会触发子组件的build()重执行8.2 不要对布局属性用 animateTo// 坏 — 修改 width 触发重新布局性能差animateTo({duration:300},(){this.width200;// 不适合动画});// 好 — 用 scale 模拟变大animateTo({duration:300},(){this.scaleValue2;// GPU 加速});width、height、margin等布局属性变化会触发整个布局树的重新计算。scale是视觉变换不影响布局性能高一个数量级。8.3 避免在动画中修改列表数据如果 100 个 ListItem 同时播放动画即使每个只改opacity也是 100 个动画同时运行。对于列表场景优先使用ListItem的内置动画如swipeAction自己写的animateTo要做好虚拟化测试。九、常见面试题 / 踩坑点9.1animateTo对哪些属性有效对State和Link装饰的变量有效包括数字、字符串、布尔值。对private普通属性无效——因为框架不追踪它们的引用变化。有效属性类型number、string、boolean、Color通过数字 RGBA9.2 动画期间用户能交互吗能。animateTo不阻塞主线程动画播放期间用户可以点击其他按钮、滚动列表。如果你不想让用户在动画期间操作例如防止重复点击需要手动加锁privateanimating:booleanfalse;privateplayAnimation():void{if(this.animating)return;// 防重入this.animatingtrue;animateTo({duration:300},(){this.valuetarget;});setTimeout((){this.animatingfalse;},300);}9.3.scale({ x: 1.5, y: 1.5 })影响子元素吗影响。如果缩放一个Row里面的所有Text、Image都会被等比缩放。包括字体大小、边框宽度、阴影半径——是真正的视觉缩放。9.4 为什么不用 CSS 动画ArkUI 的动画体系师从 Flutter不是 Web 的 CSS 动画。核心区别CSS 动画声明式分离在样式表中ArkUI 动画命令式内嵌在业务逻辑中animateTo和状态修改紧密关联这种设计的好处是动画 状态过渡——不需要额外的动画变量和 keyframe改状态就自动有动画。十、运行方式代码位于dev/entry/src/main/ets/pages/AnimationPage.ets。用 DevEco Studio 打开dev/项目首页点击动画演示 — 缩放旋转淡入平移即可体验页面展示五张 Demo 卡片每张有独立动画点击触发缩放→ 蓝色圆圈放大 1.5 倍后弹回点击触发旋转→ 箭头图标旋转 360°再点一次反向旋转点击触发平移→ 黄色方块向右滑 60vp再点滑回点击触发淡入淡出→ 紫色色块变淡到 0.15再点恢复点击全部触发→ 金色星星同时缩放、旋转、变淡十一、扩展方向连续动画序列— 用setTimeout链实现 A→B→C→D 多阶段动画手势驱动的动画— 在PanGesture回调中用animateTo实现跟手动画页面转场动画— 在router.pushUrl时配置自定义转场效果列表动画— 结合ForEach的增删用animateTo让新元素滑入、删除的元素淡出骨架屏切换— 数据加载完成后骨架灰块用opacity淡出真实内容淡入物理弹簧— 使用Curve.Spring或interpolatingSpring实现更自然的物理效果