文章目录前言一、属性动画.animation()1.1 最简单的动画写法1.2 常用缓动曲线二、显式动画animateTo()2.1 animateTo 与 .animation() 的区别三、MapKit ScaleAnimation项目核心动画3.1 源码分析3.2 ScaleAnimation 参数详解四、转场动画组件进出场4.1 if/else 触发的进出场动画4.2 常用转场效果组合五、综合实战带动画的加油站卡片列表总结前言一个没有动画的 App 像一本静态图册而合理的动画能传递状态变化、引导用户视线、提升操作反馈感。HarmonyOS ArkUI 提供了多层次的动画系统属性动画.animation()、显式动画animateTo()、转场动画transition以及MapKit 专属的 ScaleAnimation。本项目中 Marker 被点击时会放大 1.5 倍就是通过map.ScaleAnimation实现的——本篇将深入讲解这个动画的原理以及 ArkUI 各类动画的实战写法。一、属性动画.animation()1.1 最简单的动画写法只需在会改变的属性后面加.animation()属性变化时自动产生动画EntryComponentstruct AttributeAnimationDemo{Statescales:number1.0;Stateopacitys:number1.0;StatebgColor:string#1A6FF5;StateisExpanded:booleanfalse;build(){Column({space:32}){// 缩放动画Image($r(app.media.startIcon)).width(80).height(80).scale({x:this.scales,y:this.scales}).animation({// ← 这里duration:300,// 动画时长毫秒curve:Curve.EaseOut,// 缓动曲线iterations:1,// 执行次数-1 无限循环playMode:PlayMode.Normal}).onClick((){this.scalesthis.scales1.0?1.5:1.0;})// 透明度动画Text(点击我淡入淡出).fontSize(16).opacity(this.opacitys).animation({duration:500,curve:Curve.Linear}).onClick((){this.opacitysthis.opacitys1.0?0.2:1.0;})// 颜色动画Button(切换颜色).backgroundColor(this.bgColor).animation({duration:400,curve:Curve.EaseInOut}).onClick((){this.bgColorthis.bgColor#1A6FF5?#52C41A:#1A6FF5;})// 尺寸动画展开/收起Column().width(80%).height(this.isExpanded?200:60).backgroundColor(#E8F0FE).borderRadius(12).animation({duration:400,curve:Curve.Linear}).onClick((){this.isExpanded!this.isExpanded;})}.padding(32).width(100%).height(100%).justifyContent(FlexAlign.Center)}}1.2 常用缓动曲线Curve 值效果适用场景Linear匀速进度条、loadingEaseIn由慢到快元素消失EaseOut由快到慢元素出现自然EaseInOut先慢后快再慢通用最自然FastOutSlowInMaterial Design 标准曲线推荐通用二、显式动画animateTo()2.1 animateTo 与 .animation() 的区别特性.animation()animateTo()触发方式属性变化时自动在闭包内手动触发控制粒度单个属性闭包内所有状态变化适用场景简单属性动画多属性同时动画EntryComponentstruct AnimateToDemo{StatecardX:number0;StatecardWidth:number200;StatecardColor:string#1A6FF5;StatecardRadius:number8;build(){Column({space:32}){// 卡片Text(加油站卡片).width(this.cardWidth).height(80).backgroundColor(this.cardColor).borderRadius(this.cardRadius).fontColor(#FFFFFF).fontSize(16).textAlign(TextAlign.Center).offset({x:this.cardX})// 触发多属性同时动画Button(展开卡片).onClick((){// animateTo 包裹的所有状态变化都会产生动画animateTo({duration:500,curve:Curve.EaseOut},(){this.cardX0;this.cardWidth320;// 宽度变化this.cardColor#52C41A;// 颜色变化this.cardRadius16;// 圆角变化// 所有这些变化同时动画});})Button(收起卡片).onClick((){animateTo({duration:500,curve:Curve.Spring},(){this.cardX0;this.cardWidth200;this.cardColor#1A6FF5;this.cardRadius8;});})}.padding(32).width(100%).height(100%).justifyContent(FlexAlign.Center)}}三、MapKit ScaleAnimation项目核心动画3.1 源码分析MapUtil.ets中的imageAnimation方法asyncimageAnimation(marker:map.Marker,imageScale:number):Promisevoid{// 创建缩放动画X轴从1倍到imageScale倍Y轴同样letanimationnewmap.ScaleAnimation(Constants.ONE,// fromX: 起始X缩放1倍imageScale,// toX: 目标X缩放Constants.ONE,// fromY: 起始Y缩放1倍imageScale// toY: 目标Y缩放);animation.setDuration(100);// 动画时长100msanimation.setFillMode(map.AnimationFillMode.FORWARDS);// 动画结束后保持最终状态marker.setAnimation(animation);// 给 Marker 设置动画marker.startAnimation();// 启动动画}调用时机// 点击 Marker → 放大this.mapController.on(markerClick,(marker){this.imageScale1.5;mapUtil.imageAnimation(marker,this.imageScale);// 放大到1.5倍});// 关闭弹窗 → 恢复原始大小onWillDismiss:((dismissSheetAction:DismissSheetAction){if(this.curMarker){this.imageScale1;mapUtil.imageAnimation(this.curMarker,this.imageScale);// 恢复1倍}dismissSheetAction.dismiss();})3.2 ScaleAnimation 参数详解// map.ScaleAnimation(fromX, toX, fromY, toY)// fromX/fromY: 动画起始缩放比例通常为1即当前大小// toX/toY: 动画目标缩放比例// 放大效果1 → 1.5newmap.ScaleAnimation(1,1.5,1,1.5);// 缩小效果1.5 → 1newmap.ScaleAnimation(1.5,1,1.5,1);// 只横向拉伸newmap.ScaleAnimation(1,2,1,1);// X轴放大2倍Y轴不变// AnimationFillMode 说明// FORWARDS: 动画结束后保持最终状态常用// BACKWARDS: 动画开始前应用初始帧// BOTH: 两者都应用// NONE: 动画结束后恢复原始状态四、转场动画组件进出场4.1 if/else 触发的进出场动画EntryComponentstruct TransitionDemo{StateshowCard:booleanfalse;build(){Column({space:24}){Button(this.showCard?隐藏卡片:显示卡片).onClick((){// 需要用 animateTo 包裹才能触发转场动画animateTo({duration:400,curve:Curve.EaseInOut},(){this.showCard!this.showCard;});}).backgroundColor(#1A6FF5).fontColor(#FFFFFF).borderRadius(20)if(this.showCard){Column({space:8}){Text(加油站详情).fontSize(18).fontWeight(FontWeight.Bold)Text(中国石化望京站).fontSize(14).fontColor(#666666)Text(距您 1.2km · 营业中).fontSize(13).fontColor(#52C41A)}.padding(20).width(90%).backgroundColor(#FFFFFF).borderRadius(16).shadow({radius:8,color:#20000000,offsetX:0,offsetY:4})// 进入动画从下方滑入 透明度从0到1.transition(TransitionEffect.asymmetric(TransitionEffect.OPACITY.animation({duration:300}).combine(TransitionEffect.translate({y:40}).animation({duration:300,curve:Curve.EaseOut})),TransitionEffect.OPACITY.animation({duration:200}).combine(TransitionEffect.translate({y:20}).animation({duration:200}))))}}.padding(32).width(100%).height(100%).justifyContent(FlexAlign.Center)}}4.2 常用转场效果组合// 淡入淡出.transition(TransitionEffect.OPACITY.animation({duration:300}))// 从上滑入.transition(TransitionEffect.translate({y:-50}).animation({duration:300}))// 从右滑入配合页面切换.transition(TransitionEffect.translate({x:100}))// 缩放 淡入适合卡片弹出.transition(TransitionEffect.OPACITY.combine(TransitionEffect.scale({x:0.8,y:0.8})).animation({duration:300,curve:Curve.EaseOut}))五、综合实战带动画的加油站卡片列表interfaceAnimStation{id:string;name:string;distance:number;isSelected:boolean;}EntryComponentstruct AnimatedStationList{Statestations:AnimStation[][{id:1,name:望京石化,distance:0.8,isSelected:false},{id:2,name:朝阳石油,distance:1.5,isSelected:false},{id:3,name:国贸壳牌,distance:2.1,isSelected:false},];selectStation(id:string):void{animateTo({duration:300,curve:Curve.EaseInOut},():void{this.stationsthis.stations.map((s:AnimStation):AnimStation{constupdated:AnimStation{id:s.id,name:s.name,distance:s.distance,isSelected:s.idid};returnupdated;});});}build(){Column({space:12}){Text(附近加油站).fontSize(20).fontWeight(FontWeight.Bold).alignSelf(ItemAlign.Start)ForEach(this.stations,(station:AnimStation){Row({space:16}){Text(⛽).fontSize(station.isSelected?32:24)// 选中时图标变大.animation({duration:300,curve:Curve.EaseOut})Column({space:4}){Text(station.name).fontSize(station.isSelected?18:15)// 选中时字体变大.fontWeight(station.isSelected?FontWeight.Bold:FontWeight.Normal).fontColor(station.isSelected?#1A6FF5:#333333).animation({duration:300})Text(${station.distance}km).fontSize(13).fontColor(station.isSelected?#1A6FF5:#999999).animation({duration:300})}.alignItems(HorizontalAlign.Start).layoutWeight(1)if(station.isSelected){Text(✓).fontSize(18).fontColor(#1A6FF5).fontWeight(FontWeight.Bold).transition(TransitionEffect.OPACITY.animation({duration:200}))}}.padding(16).width(100%).backgroundColor(station.isSelected?#E8F0FE:#FFFFFF).borderRadius(12).border({width:station.isSelected?2:0,color:#1A6FF5}).animation({duration:300,curve:Curve.EaseInOut}).onClick((){this.selectStation(station.id);}).shadow(station.isSelected?{radius:12,color:#201A6FF5,offsetX:0,offsetY:4}:{radius:0,color:#00000000,offsetX:0,offsetY:0})},(s:AnimStation)s.id)}.padding(24).width(100%).height(100%).backgroundColor(#F5F7FA)}}总结ArkUI 动画分三层.animation()最简单属性自动动画animateTo()最灵活多属性同时动画transition处理组件进出场。MapKit 的ScaleAnimation是地图专属 API用于让 Marker 在点击时产生放大缩小的视觉反馈。合理运用动画能让应用体验从可用升级到好用是 HarmonyOS 开发中不可忽视的细节。
HarmonyOS ArkUI 动画完全指南:属性动画、显式动画与组件动画
发布时间:2026/6/8 14:57:23
文章目录前言一、属性动画.animation()1.1 最简单的动画写法1.2 常用缓动曲线二、显式动画animateTo()2.1 animateTo 与 .animation() 的区别三、MapKit ScaleAnimation项目核心动画3.1 源码分析3.2 ScaleAnimation 参数详解四、转场动画组件进出场4.1 if/else 触发的进出场动画4.2 常用转场效果组合五、综合实战带动画的加油站卡片列表总结前言一个没有动画的 App 像一本静态图册而合理的动画能传递状态变化、引导用户视线、提升操作反馈感。HarmonyOS ArkUI 提供了多层次的动画系统属性动画.animation()、显式动画animateTo()、转场动画transition以及MapKit 专属的 ScaleAnimation。本项目中 Marker 被点击时会放大 1.5 倍就是通过map.ScaleAnimation实现的——本篇将深入讲解这个动画的原理以及 ArkUI 各类动画的实战写法。一、属性动画.animation()1.1 最简单的动画写法只需在会改变的属性后面加.animation()属性变化时自动产生动画EntryComponentstruct AttributeAnimationDemo{Statescales:number1.0;Stateopacitys:number1.0;StatebgColor:string#1A6FF5;StateisExpanded:booleanfalse;build(){Column({space:32}){// 缩放动画Image($r(app.media.startIcon)).width(80).height(80).scale({x:this.scales,y:this.scales}).animation({// ← 这里duration:300,// 动画时长毫秒curve:Curve.EaseOut,// 缓动曲线iterations:1,// 执行次数-1 无限循环playMode:PlayMode.Normal}).onClick((){this.scalesthis.scales1.0?1.5:1.0;})// 透明度动画Text(点击我淡入淡出).fontSize(16).opacity(this.opacitys).animation({duration:500,curve:Curve.Linear}).onClick((){this.opacitysthis.opacitys1.0?0.2:1.0;})// 颜色动画Button(切换颜色).backgroundColor(this.bgColor).animation({duration:400,curve:Curve.EaseInOut}).onClick((){this.bgColorthis.bgColor#1A6FF5?#52C41A:#1A6FF5;})// 尺寸动画展开/收起Column().width(80%).height(this.isExpanded?200:60).backgroundColor(#E8F0FE).borderRadius(12).animation({duration:400,curve:Curve.Linear}).onClick((){this.isExpanded!this.isExpanded;})}.padding(32).width(100%).height(100%).justifyContent(FlexAlign.Center)}}1.2 常用缓动曲线Curve 值效果适用场景Linear匀速进度条、loadingEaseIn由慢到快元素消失EaseOut由快到慢元素出现自然EaseInOut先慢后快再慢通用最自然FastOutSlowInMaterial Design 标准曲线推荐通用二、显式动画animateTo()2.1 animateTo 与 .animation() 的区别特性.animation()animateTo()触发方式属性变化时自动在闭包内手动触发控制粒度单个属性闭包内所有状态变化适用场景简单属性动画多属性同时动画EntryComponentstruct AnimateToDemo{StatecardX:number0;StatecardWidth:number200;StatecardColor:string#1A6FF5;StatecardRadius:number8;build(){Column({space:32}){// 卡片Text(加油站卡片).width(this.cardWidth).height(80).backgroundColor(this.cardColor).borderRadius(this.cardRadius).fontColor(#FFFFFF).fontSize(16).textAlign(TextAlign.Center).offset({x:this.cardX})// 触发多属性同时动画Button(展开卡片).onClick((){// animateTo 包裹的所有状态变化都会产生动画animateTo({duration:500,curve:Curve.EaseOut},(){this.cardX0;this.cardWidth320;// 宽度变化this.cardColor#52C41A;// 颜色变化this.cardRadius16;// 圆角变化// 所有这些变化同时动画});})Button(收起卡片).onClick((){animateTo({duration:500,curve:Curve.Spring},(){this.cardX0;this.cardWidth200;this.cardColor#1A6FF5;this.cardRadius8;});})}.padding(32).width(100%).height(100%).justifyContent(FlexAlign.Center)}}三、MapKit ScaleAnimation项目核心动画3.1 源码分析MapUtil.ets中的imageAnimation方法asyncimageAnimation(marker:map.Marker,imageScale:number):Promisevoid{// 创建缩放动画X轴从1倍到imageScale倍Y轴同样letanimationnewmap.ScaleAnimation(Constants.ONE,// fromX: 起始X缩放1倍imageScale,// toX: 目标X缩放Constants.ONE,// fromY: 起始Y缩放1倍imageScale// toY: 目标Y缩放);animation.setDuration(100);// 动画时长100msanimation.setFillMode(map.AnimationFillMode.FORWARDS);// 动画结束后保持最终状态marker.setAnimation(animation);// 给 Marker 设置动画marker.startAnimation();// 启动动画}调用时机// 点击 Marker → 放大this.mapController.on(markerClick,(marker){this.imageScale1.5;mapUtil.imageAnimation(marker,this.imageScale);// 放大到1.5倍});// 关闭弹窗 → 恢复原始大小onWillDismiss:((dismissSheetAction:DismissSheetAction){if(this.curMarker){this.imageScale1;mapUtil.imageAnimation(this.curMarker,this.imageScale);// 恢复1倍}dismissSheetAction.dismiss();})3.2 ScaleAnimation 参数详解// map.ScaleAnimation(fromX, toX, fromY, toY)// fromX/fromY: 动画起始缩放比例通常为1即当前大小// toX/toY: 动画目标缩放比例// 放大效果1 → 1.5newmap.ScaleAnimation(1,1.5,1,1.5);// 缩小效果1.5 → 1newmap.ScaleAnimation(1.5,1,1.5,1);// 只横向拉伸newmap.ScaleAnimation(1,2,1,1);// X轴放大2倍Y轴不变// AnimationFillMode 说明// FORWARDS: 动画结束后保持最终状态常用// BACKWARDS: 动画开始前应用初始帧// BOTH: 两者都应用// NONE: 动画结束后恢复原始状态四、转场动画组件进出场4.1 if/else 触发的进出场动画EntryComponentstruct TransitionDemo{StateshowCard:booleanfalse;build(){Column({space:24}){Button(this.showCard?隐藏卡片:显示卡片).onClick((){// 需要用 animateTo 包裹才能触发转场动画animateTo({duration:400,curve:Curve.EaseInOut},(){this.showCard!this.showCard;});}).backgroundColor(#1A6FF5).fontColor(#FFFFFF).borderRadius(20)if(this.showCard){Column({space:8}){Text(加油站详情).fontSize(18).fontWeight(FontWeight.Bold)Text(中国石化望京站).fontSize(14).fontColor(#666666)Text(距您 1.2km · 营业中).fontSize(13).fontColor(#52C41A)}.padding(20).width(90%).backgroundColor(#FFFFFF).borderRadius(16).shadow({radius:8,color:#20000000,offsetX:0,offsetY:4})// 进入动画从下方滑入 透明度从0到1.transition(TransitionEffect.asymmetric(TransitionEffect.OPACITY.animation({duration:300}).combine(TransitionEffect.translate({y:40}).animation({duration:300,curve:Curve.EaseOut})),TransitionEffect.OPACITY.animation({duration:200}).combine(TransitionEffect.translate({y:20}).animation({duration:200}))))}}.padding(32).width(100%).height(100%).justifyContent(FlexAlign.Center)}}4.2 常用转场效果组合// 淡入淡出.transition(TransitionEffect.OPACITY.animation({duration:300}))// 从上滑入.transition(TransitionEffect.translate({y:-50}).animation({duration:300}))// 从右滑入配合页面切换.transition(TransitionEffect.translate({x:100}))// 缩放 淡入适合卡片弹出.transition(TransitionEffect.OPACITY.combine(TransitionEffect.scale({x:0.8,y:0.8})).animation({duration:300,curve:Curve.EaseOut}))五、综合实战带动画的加油站卡片列表interfaceAnimStation{id:string;name:string;distance:number;isSelected:boolean;}EntryComponentstruct AnimatedStationList{Statestations:AnimStation[][{id:1,name:望京石化,distance:0.8,isSelected:false},{id:2,name:朝阳石油,distance:1.5,isSelected:false},{id:3,name:国贸壳牌,distance:2.1,isSelected:false},];selectStation(id:string):void{animateTo({duration:300,curve:Curve.EaseInOut},():void{this.stationsthis.stations.map((s:AnimStation):AnimStation{constupdated:AnimStation{id:s.id,name:s.name,distance:s.distance,isSelected:s.idid};returnupdated;});});}build(){Column({space:12}){Text(附近加油站).fontSize(20).fontWeight(FontWeight.Bold).alignSelf(ItemAlign.Start)ForEach(this.stations,(station:AnimStation){Row({space:16}){Text(⛽).fontSize(station.isSelected?32:24)// 选中时图标变大.animation({duration:300,curve:Curve.EaseOut})Column({space:4}){Text(station.name).fontSize(station.isSelected?18:15)// 选中时字体变大.fontWeight(station.isSelected?FontWeight.Bold:FontWeight.Normal).fontColor(station.isSelected?#1A6FF5:#333333).animation({duration:300})Text(${station.distance}km).fontSize(13).fontColor(station.isSelected?#1A6FF5:#999999).animation({duration:300})}.alignItems(HorizontalAlign.Start).layoutWeight(1)if(station.isSelected){Text(✓).fontSize(18).fontColor(#1A6FF5).fontWeight(FontWeight.Bold).transition(TransitionEffect.OPACITY.animation({duration:200}))}}.padding(16).width(100%).backgroundColor(station.isSelected?#E8F0FE:#FFFFFF).borderRadius(12).border({width:station.isSelected?2:0,color:#1A6FF5}).animation({duration:300,curve:Curve.EaseInOut}).onClick((){this.selectStation(station.id);}).shadow(station.isSelected?{radius:12,color:#201A6FF5,offsetX:0,offsetY:4}:{radius:0,color:#00000000,offsetX:0,offsetY:0})},(s:AnimStation)s.id)}.padding(24).width(100%).height(100%).backgroundColor(#F5F7FA)}}总结ArkUI 动画分三层.animation()最简单属性自动动画animateTo()最灵活多属性同时动画transition处理组件进出场。MapKit 的ScaleAnimation是地图专属 API用于让 Marker 在点击时产生放大缩小的视觉反馈。合理运用动画能让应用体验从可用升级到好用是 HarmonyOS 开发中不可忽视的细节。