目录
一、属性动画
组件的某些通用属性变化时,可以通过属性动画实现渐变过渡效果。支持的属性包括width、height、backgroundColor、opacity、scale、rotate、translate等。
把animation接口加在要做属性动画的可动画属性后即可。animation只要检测到其绑定的可动画属性发生变化,就会自动添加属性动画。
animation(value:AnimateParam): T
设置组件的属性动画。
AnimateParam对象说明
- duration:动画持续时间,默认为1000
- tempo:动画速度为,值越大,速度越快,值越小,速度越小。为0时无动画效果。默认值:1.0;取值范围:[0, +∞)。
- curve:Curve/string 动画曲线
- delay:延迟播放时间
typescript
@Entry
@Component
struct AnimationExample {
@State widthSize: number = 250;
@State heightSize: number = 100;
@State rotateAngle: number = 0;
@State flag: boolean = true;
@State space: number = 10;
build() {
Column() {
Column({ space: this.space }) // 改变Column构造器中的space动画不生效
.onClick(() => {
if (this.flag) {
this.widthSize = 150;
this.heightSize = 60;
this.space = 20; // 改变this.space动画不生效
} else {
this.widthSize = 250;
this.heightSize = 100;
this.space = 10; // 改变this.space动画不生效
}
this.flag = !this.flag;
})
.backgroundColor(Color.Black)
.margin(30)
.width(this.widthSize) // 只有写在animation前面才生效
.height(this.heightSize) // 只有写在animation前面才生效
.animation({
duration: 2000,
curve: Curve.EaseOut,
iterations: 3,
playMode: PlayMode.Normal
})
// .width(this.widthSize) // 动画不生效
// .height(this.heightSize) // 动画不生效
}
}
}
二、显示动画
提供全局animateTo显式动画接口来指定由于闭包代码导致的状态变化插入过渡动效。同属性动画,布局类改变宽高的动画,内容都是直接到终点状态,例如文字、Canvas的内容等,如果要内容跟随宽高变化,可以使用renderFit属性配置。
animateTo(value: AnimateParam, event: () => void): void
显式动画接口。在需要动画时,显式调用该接口改变状态以产生动画。
- 在组件出现时显示动画
typescript
@Entry
@Component
struct Index {
@State widthSize: number = 250
@State heightSize: number = 100
@State flag: boolean = true
@State rotateAngle: number = 0
build() {
Column() {
Button('Change Size')
.onClick(() => {
if (this.flag) {
this.getUIContext()?.animateTo({
duration: 2000,
curve: Curve.EaseOut,
iterations: 3,
playMode: PlayMode.Normal
},()=> {
this.widthSize = 150
this.heightSize = 60
})
} else {
this.getUIContext()?.animateTo({},() => {
this.widthSize = 250
this.heightSize = 100
})
}
this.flag = !this.flag
})
.width(this.widthSize)
.height(this.heightSize)
Button('stop rotate')
.margin(50)
.rotate({x: 0,y:0,z:1,angle: this.rotateAngle})
.onAppear(() => {
this.getUIContext()?.animateTo({
duration: 1200,
curve: Curve.Friction,
delay: 500, //延迟播放时间
iterations: -1, //无限循环次播放
playMode: PlayMode.Alternate,
//动画在奇数次(1、3、5...)正向播放,在偶数次(2、4、6...)反向播放。
expectedFrameRateRange: { //期待帧率
min: 10,
max: 10,
expected: 60
}
},() => {
this.rotateAngle = 90
})
})
.onClick(() => {
this.getUIContext()?.animateTo({
duration: 0
}, ()=>{
this.rotateAngle = 0
})
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}

三、转场动画
1.页面间转场
当路由(router)进行切换时,可以通过在pageTransition函数中自定义页面入场和页面退场的转场动效。
PageTransitionEnter(value: PageTransitionOptions)
设置当前页面的自定义入场动效。onEnter(event: PageTransitionCallback): PageTransitionEnterInterface
逐帧回调,直到入场动画结束,progress从0变化到1。PageTransitionExit(value: PageTransitionOptions)
设置当前页面的自定义退场动效。继承自CommonTransitiononExit(event: PageTransitionCallback): PageTransitionExitInterface
逐帧回调,直到出场动画结束,progress从0变化到1。
PageTransitionOptions对象说明
支持设备PhonePC/2in1TabletTVWearable
元服务API: 从API version 11开始,该接口支持在元服务中使用。
系统能力: SystemCapability.ArkUI.ArkUI.Full
名称 | 类型 | 说明 |
---|---|---|
type | RouteType | 页面转场效果生效的路由类型。默认值:RouteType.None。 |
duration | number | 动画的时长;单位:毫秒;默认值:1000;取值范围:[0, +∞) |
curve | Curve /string / ICurve | 动画曲线。推荐以Curve或ICurve形式指定。当类型为string时,为动画插值曲线,取值参考AnimateParam的curve参数。默认值:Curve.Linear |
delay | number | 动画延迟时长;单位:毫秒;默认值:0 |
设置退入场动画
- Index.ets
typescript
@Entry
@Component
struct Index {
@State opacity1: number = 1;
@State scale1: number = 1;
build() {
Column() {
Image($r('app.media.sunbg'))
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
.scale({x: this.scale1})
.opacity(this.opacity1)
.onClick(() => {
this.getUIContext().getRouter().pushUrl({url: 'pages/Page1'})
})
}
pageTransition() {
//设置当前页的自定义入场动效
PageTransitionEnter({duration: 1200, curve: Curve.Linear})
//逐帧回调,直至动画结束
.onEnter((type: RouteType, progress: number ) => {
if (type == RouteType.Push || type == RouteType.Pop) {
this.opacity1 = progress
this.scale1 = progress //设置转场时的缩放效果
}
})
//设置当前页的自定义退场动效
PageTransitionExit({duration: 1200, curve: Curve.Ease})
.onExit((type: RouteType, progress: number) => {
if (type == RouteType.Push) {
this.opacity1 = 1 - progress
this.scale1 = 1 - progress
}
})
}
}
- Page.ets
typescript
@Entry
@Component
struct Page1 {
@State opacity2: number = 1;
@State scale2: number = 1;
build() {
Column() {
Image($r('app.media.wubg'))
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
.scale({x: this.scale2})
.opacity(this.opacity2)
.onClick(() => {
this.getUIContext().getRouter().pushUrl({url: 'pages/Index'})
})
}
pageTransition() {
PageTransitionEnter({duration: 1200, curve: Curve.Linear})
.onEnter((type: RouteType, progress: number ) => {
if (type == RouteType.Push || type == RouteType.Pop) {
this.scale2 = progress
}
this.opacity2 = progress
})
PageTransitionExit({duration: 1200, curve: Curve.Ease})
.onExit((type: RouteType, progress: number) => {
if (type == RouteType.Pop) {
this.opacity2 = 1 - progress
this.scale2 = 1 - progress
}
})
}
}

通过不同的退入场类型配置不同的退场,入场动画。
- Index.ets
typescript
@Entry
@Component
struct Index {
@State opacity1: number = 1;
@State scale1: number = 1;
build() {
Column() {
Image($r('app.media.sunbg'))
.width('100%')
.height('100%')
}
// .width('100%')
// .height('100%')
// .scale({x: this.scale1})
// .opacity(this.opacity1)
.onClick(() => {
this.getUIContext().getRouter().pushUrl({url: 'pages/Page1'})
})
}
pageTransition() {
//设置当前页的自定义入场动效
PageTransitionEnter({duration: 1200})
.slide(SlideEffect.Left)
//设置当前页的自定义退场动效
PageTransitionExit({duration: 1000})
.translate({x: 100.0, y: 100.0})
.opacity(0)
}
}
- Page.ets
typescript
@Entry
@Component
struct Page1 {
@State opacity2: number = 1;
@State scale2: number = 1;
build() {
Column() {
Image($r('app.media.wubg'))
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
.scale({x: this.scale2})
.opacity(this.opacity2)
.onClick(() => {
this.getUIContext().getRouter().pushUrl({url: 'pages/Index'})
})
}
pageTransition() {
PageTransitionEnter({duration: 1000})
.slide(SlideEffect.Left)
PageTransitionExit({duration: 1200})
.translate({x: 100.0, y: 100.0})
.opacity(0)
}
}

设置退入场平移效果
Index.ets
typescript
@Entry
@Component
struct Index {
@State scale1: number = 1
@State opacity1: number = 1
build() {
Column() {
Button('页面')
.onClick(() => {
this.getUIContext()?.getRouter()
.pushUrl({url: 'pages/Page1'})
})
.width(200)
.height(60)
.fontSize(36)
Text('START')
.fontSize(36)
.textAlign(TextAlign.Center)
}
.scale({x: this.scale1})
.opacity(this.opacity1)
.height('100%')
.width('100%')
.justifyContent(FlexAlign.Center)
}
pageTransition() {
PageTransitionEnter({duration: 200})
.slide(SlideEffect.START)
PageTransitionExit({duration: 100})
.slide(SlideEffect.START)
}
}
typescript
@Entry
@Component
struct Page {
@State scale1: number = 1;
@State opacity1: number = 1;
build() {
Column() {
Button('Page2')
.onClick(() => {
this.getUIContext().getRouter().pushUrl({
url: "pages/Index"
});
})
.width(200)
.height(60)
.fontSize(36)
Text('END')
.fontSize(36)
.textAlign(TextAlign.Center)
}
.scale({x: this.scale1})
.opacity(this.opacity1)
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
// pageTransition() {
// PageTransitionEnter({duration: 200})
// .slide(SlideEffect.END)
// PageTransitionExit({duration: 100})
// .slide(SlideEffect.END)
// }
}

2.组件内转场
transition(value: TransitionOptions | TransitionEffect): T
组件插入显示和删除隐藏的过渡效果。
属性
名称 | 说明 |
---|---|
IDENTITY | 禁用转场效果。 |
OPACITY | 为组件添加透明度转场效果,出现时透明度从0到1、消失时透明度从1到0,相当于TransitionEffect.opacity(0)。 |
SLIDE | 从START边滑入,END边滑出。即在LTR模式下,从左侧滑入,右侧滑出;在RTL模式下,从右侧滑入,左侧滑出。 |
SLIDE_SWITCH | 指定出现时从右先缩小再放大侧滑入、消失时从左侧先缩小再放大滑出的转场效果。自带动画参数,也可覆盖动画参数,自带的动画参数时长600ms,指定动画曲线cubicBezierCurve(0.24, 0.0, 0.50, 1.0),最小缩放比例为0.8。 |
- 使用不同接口实现图片的出现消失
typescript
@Entry
@Component
struct Index {
//使用不同接口实现图片出现消失
@State flag: boolean = false
@State show: string = 'show'
build() {
Column() {
Button(this.show)
.width(80)
.height(30)
.margin(30)
.onClick(() => {
if (this.flag) {
this.show = 'hide'
} else {
this.show = 'show'
}
this.getUIContext().animateTo({duration: 2000}, () => {
this.flag = !this.flag
})
})
if (this.flag) {
Image($r('app.media.sunbg'))
.width(200)
.height(200)
// .transition(TransitionEffect.OPACITY.animation({duration: 3000, curve: Curve.Ease}).combine(
// TransitionEffect.rotate({z: 1, angle: 180})
// ))
.transition(
TransitionEffect.asymmetric(
TransitionEffect.OPACITY.animation({duration: 1000}).combine(
TransitionEffect.rotate({z: 1, angle: 180})
.animation({duration: 1000})
),
TransitionEffect.OPACITY.animation({delay: 1000, duration: 1000}).combine(
TransitionEffect.rotate({z: 1, angle: 180}).animation({duration: 1000})
)
))
Image($r('app.media.sunbg'))
.width(200)
.height(200)
.margin({top: 100})
.transition(TransitionEffect.asymmetric(
TransitionEffect.scale({x: 0, y: 0}),
TransitionEffect.IDENTITY
))
}
}
.justifyContent(FlexAlign.Center)
.width('100%')
}

- 设置父组件为transition
typescript
@Entry
@Component
struct Index {
// 设置父组件为transition
@State flag: boolean = true;
@State show: string = 'hide'
build() {
Column() {
Button(this.show)
.margin({top:30})
.width(80)
.height(30)
.onClick(() => {
if (this.flag) {
this.show = 'show'
} else {
this.show = 'hide'
}
this.flag = !this.flag
})
if (this.flag) {
Column() {
Row() {
Image($r('app.media.sunbg'))
.width(150)
.height(150)
.margin({top: 50})
.id('image1')
.transition(TransitionEffect.OPACITY.animation({duration: 1000}))
}
Image($r('app.media.sunbg'))
.width(150)
.height(150)
.margin({top: 50})
.id('image2')
.transition(TransitionEffect.scale({x: 0, y: 0}).animation({duration: 1000}))
Text('View')
.margin({top: 50})
}
.id('column1')
.transition(TransitionEffect.opacity(0.99).animation({duration: 1000}),
(transition: boolean)=>{
console.info('transition finish, transitionIn:'+transition)
})
}
}
.width('100%')
.justifyContent(FlexAlign.Center)
}
}
