HarmonyOS动画:属性动画、显示动画、转场动画
目录
- 一、属性动画
- 二、显示动画
- 三、转场动画
- 1.页面间转场
- 设置退入场动画
- 设置退入场平移效果
- 2.组件内转场
一、属性动画
组件的某些通用属性变化时,可以通过属性动画实现渐变过渡效果。支持的属性包括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:延迟播放时间
@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
显式动画接口。在需要动画时,显式调用该接口改变状态以产生动画。
- 在组件出现时显示动画
@Entry
@Component
struct Index {@State widthSize: number = 250@State heightSize: number = 100@State flag: boolean = true@State rotateAngle: number = 0build() {Column() {Button('Change Size').onClick(() => {if (this.flag) {this.getUIContext()?.animateTo({duration: 2000,curve: Curve.EaseOut,iterations: 3,playMode: PlayMode.Normal},()=> {this.widthSize = 150this.heightSize = 60})} else {this.getUIContext()?.animateTo({},() => {this.widthSize = 250this.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
@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 = progressthis.scale1 = progress //设置转场时的缩放效果}})//设置当前页的自定义退场动效PageTransitionExit({duration: 1200, curve: Curve.Ease}).onExit((type: RouteType, progress: number) => {if (type == RouteType.Push) {this.opacity1 = 1 - progressthis.scale1 = 1 - progress}})}}
- Page.ets
@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 - progressthis.scale2 = 1 - progress}})}
}
通过不同的退入场类型配置不同的退场,入场动画。
- Index.ets
@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
@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
@Entry
@Component
struct Index {@State scale1: number = 1@State opacity1: number = 1build() {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)}
}
@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。 |
- 使用不同接口实现图片的出现消失
@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
@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)}
}