动画分类
1. 属性动画 .animation() —— 挂在组件上,属性一变自动平滑过渡
2. 显式动画 animateTo() —— 把改状态的代码包起来,主动触发动效
3. 页面间转场 NavDestination.systemTransition —— 页面切换动画
4. 组件内转场 .transition() —— 组件出现/消失时的特效
5. 关键帧动画 keyframeAnimateTo() —— 一个动画分好几段控制
6. 路径动画 .motionPath() —— 组件沿自定义路径移动
属性动画
• 能动画:宽高、背景色、透明度、缩放、旋转、位移
• 不能:zIndex、focusable 这种值变了画面不一定变
就是给 Column 加 .animation(),我的理解就是给它加一个动画效果.animation ({ })里加动画实现的效果条件比如动画时长以及运行次数等
.animation({
duration: 800, // 动画时长
curve: Curve.EaseInOut, // 曲线
iterations: 1 // 次数,-1 无限循环
})
曲线解释
• Linear:匀速
• EaseIn:慢到快
• EaseOut:快到慢
• EaseInOut:缓入缓出
• Spring:弹性回弹
学习成果
import { curves } from '@kit.ArkUI'
@Entry
@Component
struct AttributeAnimationDemo{
@State boxWidth:number=100
@State boxHeight:number=100
build() {
Column({space:20}){
Column()
.width(this.boxWidth)
.height(this.boxHeight)
.backgroundColor('#007AFF')
.borderRadius(20)
.animation({
duration:700,
curve:Curve.EaseInOut ,
iterations:1
})
Button('缩小')
.onClick( ()=>{
this.boxWidth=50
this.boxHeight=50
})
.width('20%')
.height('10%')
.padding(20)
Button('放大')
.onClick( ()=>{
this.boxWidth=200
this.boxHeight=200
})
.width('20%')
.height('10%')
.padding(20)
}
.justifyContent(FlexAlign.Center)
}
}
运行视频
属性
显式动画
注意事项
1. 别在 aboutToAppear / aboutToDisappear 里调动画——build 还没执行,属性没初值,动画出不来
2. 状态管理 V2 里用 animateTo 可能出异常效果,要留意
3. animateTo 能做宽高动画,但内容(文字、Canvas)默认直接跳到终点,要跟随变化得配 renderFit
4. 组件出现/消失的效果,显式动画不好做的话,就用 .transition()
animate to与箭头函数联用
1.控制状态
Button('切换卡片')
.onClick(() => {
animateTo({ duration: 500, curve: Curve.EaseInOut }, () => {
this.showCard = !this.showCard
})
})
2.一次修改多个属性
animateTo({ duration: 800, curve: Curve.EaseInOut }, () => {
this.boxWidth = 160
this.boxHeight = 160
this.opacity = 1
})
属性动画与显式动画的区别
• 属性动画是"这个组件以后属性变了都要动画"——声明一次,永久生效
• 显式动画是"这次修改要有动画"——每次都要主动写
属性动画:
Column()
.width(this.boxWidth)
.height(this.boxHeight)
.animation({ duration: 800, curve: Curve.EaseInOut })
Button('放大').onClick(() => {
this.boxWidth = 160
this.boxHeight = 160
})
显式动画
Column()
.width(this.boxWidth)
.height(this.boxHeight)
Button('放大').onClick(() => {
animateTo({ duration: 800, curve: Curve.EaseInOut }, () => {
this.boxWidth = 160
this.boxHeight = 160
})
})
转场动画
组内转场
if加transition ➕animate to
if (this.showCard) {
Text('卡片')
.transition(TransitionEffect.opacity(0).animation({ duration: 500 }))
}
animateTo({ duration: 500 }, () => {
this.showCard = !this.showCard
})
1.透明度过渡:TransitionEffect.opacity(0)
2.缩放:TransitionEffect.scale({ x: 0.5, y: 0.5 })
3.平移:TransitionEffect.translate({ x: 200, y: 0 })
4.旋转:TransitionEffect.rotate({ z: 1, angle: 180 })
5.边缘移入/出:TransitionEffect.move(TransitionEdge.BOTTOM)
6.非对称:TransitionEffect.asymmetric(进, 出)
六种动画对比
下面用一张表格汇总属性动画、显式动画、组件内转场、页面间转场、关键帧动画和路径动画的核心差异,方便快速查阅。
| 动画类型 | 触发方式 | 适用场景 | 典型 API | 注意事项 |
|---|---|---|---|---|
| 属性动画 | 组件属性变化时自动触发,声明一次永久生效 | 宽高、背景色、透明度、缩放、旋转、位移等属性持续变化的场景 | .animation() | zIndex、focusable 等不影响画面的属性不会产生动画;不要在 aboutToAppear / aboutToDisappear 中调用 |
| 显式动画 | 把修改状态的代码包进 animateTo 回调,主动触发动效 | 需要精确控制某一次状态变更的动画效果,如按钮点击后的宽高变化 | animateTo() | 状态管理 V2 下可能出现异常效果;宽高动画中文字、Canvas 默认直接跳到终点,需配合 renderFit 跟随变化 |
| 组件内转场 | 组件出现或消失时,配合 if 条件渲染和 animateTo 触发 | 组件插入、移除时的过渡效果,如卡片显示与隐藏 | .transition()、TransitionEffect | 支持透明度、缩放、平移、旋转、边缘移入移出等效果;非对称转场用 asymmetric(进, 出) 分别设置进场和退场 |
| 页面间转场 | 页面切换时由系统或自定义配置触发 | 页面跳转、返回时的过渡动画 | NavDestination.systemTransition | 页面级动画,与组件内转场作用范围不同;自定义时需注意与系统默认转场的协调 |
| 关键帧动画 | 通过 keyframeAnimateTo 分段控制动画过程 | 一个动画需要分多段、每段使用不同曲线或时长的场景 | keyframeAnimateTo() | 适合复杂节奏的动画编排;每段关键帧的时长、曲线需单独配置 |
| 路径动画 | 通过 .motionPath() 指定路径,组件沿路径移动 | 组件沿自定义曲线、折线等路径运动的场景 | .motionPath() | 路径坐标需要提前规划好;路径动画通常只影响位置,不改变组件其他属性 |
简单来说:属性动画和显式动画解决的是「属性变化要不要动」的问题,前者声明一次、后者每次主动写;组件内转场和页面间转场解决的是「组件或页面出现/消失」的问题;关键帧动画和路径动画则偏向更精细的动画编排,分别控制分段节奏和运动轨迹。
对称转场和非对称转场
对称
Text('Hello')
.transition(
TransitionEffect.translate({ x: 200 })
.combine(TransitionEffect.opacity(0))
.animation({ duration: 500 })
)
进场与退场效果相同
.combine 的作用是将两个效果重叠起来
非对称
if (this.showCard) {
Text('卡片')
.transition(
TransitionEffect.asymmetric(
// 第一个参数:进场效果
TransitionEffect.opacity(0)
.combine(TransitionEffect.scale({ x: 0.5, y: 0.5 }))
.animation({ duration: 500 }),
// 第二个参数:退场效果
TransitionEffect.move(TransitionEdge.BOTTOM)
.animation({ duration: 400 })
)
)
}
1. 参数顺序:asymmetric(进, 出) 第一个是插入效果,第二个是退场效果,
2. 时长可以不一样:进场 500ms、退场 400ms,各自独立,互不影响
成果
@Entry
@Component
struct AnimateToDemo {
@State showCard: boolean = true
build() {
Column({ space: 20 }) {
if (this.showCard) {
Column() {
Text('我是卡片')
.fontSize(20)
.fontColor(Color.White)
}
.width(150)
.height(150)
.backgroundColor('#34C759')
.borderRadius(20)
.justifyContent(FlexAlign.Center)
.transition(
TransitionEffect.asymmetric(
TransitionEffect.opacity(0)
.combine(TransitionEffect.scale({ x: 0.5, y: 0.5 }))
.animation({ duration: 500, curve: Curve.EaseOut }),
TransitionEffect.move(TransitionEdge.BOTTOM)
.animation({ duration: 400, curve: Curve.EaseIn })
)
)
}
Button('隐藏')
.width(220)
.fontSize(16)
.backgroundColor('#FF3B30')
.onClick(() => {
animateTo({ duration: 500 }, () => {
this.showCard = false
})
})
Button('显示')
.width(220)
.fontSize(16)
.backgroundColor('#007AFF')
.onClick(() => {
animateTo({ duration: 500 }, () => {
this.showCard = true
})
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
转场加
粒子动画
Particle:粒子组件,鸿蒙用来制作粒子动画的UI组件,可以把它想成装粒子的大盒子。
emitter:发射器,定义粒子从哪里产生、以何种方式生成,容易理解的想成喷泉的喷头。
particle:粒子本身,控制粒子长什么样子
color:粒子颜色,设置粒子的色彩
acceleration:加速度,控制粒子飞行方向与运动速度,
opacity:透明度变化,控制粒子透明程度的变化
updater:更新器,让粒子属性可以随时间自动变化
config:配置,给功能填写各项参数
disturbanceFields:扰动场,给粒子施加外力
代码骨架
@Entry
@Component
struct XxxEffect {
build() {
Stack() {
// 背景
Text().width('100%').height('100%').backgroundColor(Color.Black)
// 粒子组件
Particle({
particles: [
{
emitter: { /* 发射器 */ },
color: { /* 颜色 */ },
opacity: { /* 透明度 */ },
acceleration: { /* 加速度 */ }
}
]
})
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
}
}
1.发射器里面的具体内容代码骨架
emitter: {
particle: {
type: ParticleType.POINT, // 粒子类型:圆点
config: { radius: 4 }, // 圆点半径
count: 200, // 同屏粒子总数
lifetime: 8000, // 每个粒子存活毫秒数
lifetimeRange: 2000 // 存活时间浮动范围
},
emitRate: 30, // 每秒发射多少个
position: [0, -50], // 发射起点坐标
shape: ParticleEmitterShape.RECTANGLE, // 发射器形状
size: ['100%', 50] // 发射窗口大小
}
2.颜色代码骨架及注释
color: {
range: ['#ffffff', '#e0e8ff'] // 从这个范围里取色
}
3.透明度基本骨架
opacity: {
range: [0.0, 1.0],
updater: {
type: ParticleUpdater.CURVE,
config: [
{
from: 0.0, // 从透明
to: 1.0, // 到不透明
startMillis: 0,
endMillis: 1000,
curve: Curve.EaseIn
},
{
from: 1.0,
to: 0.0, // 到透明
startMillis: 6000,
endMillis: 8000,
curve: Curve.EaseOut
}
]
}
}
from/to = 从哪变到哪;startMillis/endMillis = 第几毫秒到第几毫秒完成。
4.加速度模版
acceleration: {
speed: {
range: [8, 18], // 速度范围 vp/秒
updater: {
type: ParticleUpdater.RANDOM, // 随机变化
config: [2, 8] // 每秒随机增减 2~8
}
},
angle: {
range: [80, 100] // 角度:0右 90下 180左 270上
}
}
关于粒子动画学习本人见解
骨架性比较强 有种套公式的感觉
学习成果
@Entry
@Component
struct SnowEffect {
build() {
Stack() {
Text()
.width('100%')
.height('100%')
.backgroundColor('#1a1a2e')
Particle({
particles: [
{
emitter: {
particle: {
type: ParticleType.POINT,
config: {
radius: 4
},
count: 200,
lifetime: 800000,
lifetimeRange: 2000
},
emitRate: 30,
position: [0, -50],
shape: ParticleEmitterShape.RECTANGLE,
size: ['100%', 50]
},
color: {
range: ['#ffffff', '#e0e8ff']
},
opacity: {
range: [0.0, 1.0],
updater: {
type: ParticleUpdater.CURVE,
config: [
{
from: 0.0,
to: 1.0,
startMillis: 0,
endMillis: 1000,
curve: Curve.EaseIn
},
{
from: 1.0,
to: 0.0,
startMillis: 6000,
endMillis: 8000,
curve: Curve.EaseOut
}
]
}
},
acceleration: {
speed: {
range: [8, 18],
updater: {
type: ParticleUpdater.RANDOM,
config: [2, 8]
}
},
angle: {
range: [80, 100]
}
}
}
]
})
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
}
}
粒子