HarmonyOS动画:属性动画、显示动画、转场动画

目录

一、属性动画

组件的某些通用属性变化时,可以通过属性动画实现渐变过渡效果。支持的属性包括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)
    设置当前页面的自定义退场动效。继承自CommonTransition
  • onExit(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)
  }
}
相关推荐
2501_919749033 小时前
鸿蒙:使用worker实现多线程通信
华为·harmonyos
安卓开发者3 小时前
鸿蒙Next应用开发:ArkTS语言下的IPC与RPC通信指南
qt·rpc·harmonyos
云雾J视界3 小时前
华为数字化实战指南:从顶层设计到行业落地的系统方法论
华为·数字化·实践·方法论·转型·变革管理·系统方法
Forever_Hopeful4 小时前
华为鸿蒙 ArkTS 实战:基于 RelationalStore 的 SQLite 实现本地数据持久化
华为·sqlite·harmonyos
程序员潘Sir8 小时前
鸿蒙应用开发从入门到实战(十三):ArkUI组件Slider&Progress
harmonyos·鸿蒙
程序员潘Sir1 天前
鸿蒙应用开发从入门到实战(十二):ArkUI组件Button&Toggle
harmonyos·鸿蒙
程序员潘Sir2 天前
鸿蒙应用开发从入门到实战(十一):ArkUI组件Text&TextInput
harmonyos·鸿蒙
程序员潘Sir3 天前
鸿蒙应用开发从入门到实战(十):ArkUI图片组件Image
harmonyos
高心星5 天前
鸿蒙应用开发——Repeat组件的使用
harmonyos