鸿蒙动画

288 阅读3分钟

属性动画

组件的某些通用属性变化时,可以通过属性动画实现渐变过渡效果,提升用户体验。支持的属性包括width、height、backgroundColor、opacity、scale、rotate、translate等

animation(value: {duration?: number, tempo?: number, curve?: string | Curve | ICurve, delay?:number, iterations: number, playMode?: PlayMode, onFinish?: () => void})

识别组件的可动画属性变化,自动添加动画,animation只会作用于在其之上的属性调用

// 示例
.animation({
  // 动画时长,默认1000毫秒
  duration: 1200,
  // 播放速度,默认为1
  tempo: 1,
  // 动画曲线
  curve: Curve.Friction,
  // 动画延迟执行时长,默认0,不延迟
  delay: 500,
  // 动画播放次数,默认1,-1:无限循环,0:无动画效果
  iterations: -1, // 设置-1表示动画无限循环
  // 动画播放模式
  // Alternate: 奇数次正向,偶数次反向
  // AlternateReverse:奇数次反向,偶数次正向
  // Reverse:反向
  // Normal:正常播放
  playMode: PlayMode.Alternate,
  // 播放完成回调
  onFinish: () => {}
})

显式动画

提供全局animateTo显式动画接口来指定由于闭包代码导致的状态变化插入过渡动效。

// AnimateParam 参数同属性动画
animateTo(value: AnimateParam, event: () => void): void

示例 animateTo({ duration: 1200, curve: Curve.Friction, delay: 500, iterations: -1, // 设置-1表示动画无限循环 playMode: PlayMode.Alternate, onFinish: () => { console.info('play end') } }, () => { this.rotateAngle = 90 }) })

转场动画

  • 页面间转场

    在全局pageTransition方法内配置页面入场和页面退场时的自定义转场动效。

    @Entry
    @Component
    struct PageTransitionExample1 {
      @State scale1: number = 1
      @State opacity1: number = 1
    
      build() {
        Column() {
          Navigator({ target: 'pages/page1', type: NavigationType.Push }) {
            Image($r('app.media.bg1')).width('100%').height('100%')    // 图片存放在media文件夹下
          }
        }.scale({ x: this.scale1 }).opacity(this.opacity1)
      }
      // 
      pageTransition() {
        PageTransitionEnter({ duration: 1200, curve: Curve.Linear })
          .onEnter((type: RouteType, progress: number) => {
            this.scale1 = 1
            this.opacity1 = progress
          }) // 进场过程中会逐帧触发onEnter回调
        PageTransitionExit({ duration: 1500, curve: Curve.Ease })
          .onExit((type: RouteType, progress: number) => {
            this.scale1 = 1 - progress
            this.opacity1 = 1
          }) // 退场过程中会逐帧触发onExit回调
      }
    }
    
  • 共享元素转场

    当路由进行切换时,可以通过设置组件的 sharedTransition 属性将该元素标记为共享元素并设置对应的共享元素转场动效。

    示例:

    // 页面A
    build() {
      Column({space: 20}) {
        Image("https://p.qqan.com/up/2024-5/20245101125138293.jpg")
          .width(200)
          .aspectRatio(1)
          .borderRadius(8)
          // 组件共享转场
          // id: 不为空的组件即位共享元素
          // duration: 时长
          // curve:曲线样式
          // delay:延迟时间
          // motionPath: 动画路径
          // type:type为SharedTransitionEffectType.Exchange时motionPath才会生效。默认值Exchange
          .sharedTransition("dog", {
            duration: 500
          })
    
        Button("跳转到B")
          .onClick(() => {
            router.pushUrl({
              url: "pages/AnimationCase/SharedPageB"
            })
          })
      }
      .width('100%')
      .height('100%')
    }
    
    // 页面B
    build() {
      Column() {
        Image("https://p.qqan.com/up/2024-5/20245101125138293.jpg")
          .width('100%')
          .aspectRatio(1)
          .borderRadius(10)
          .sharedTransition("dog", {
            duration: 500
          })
      }
      .width('100%')
      .height('100%')
    }
    
  • 组件内转场

    组件内转场主要通过transition属性配置转场参数,在组件插入和删除时显示过渡动效,主要用于容器组件中的子组件插入和删除时,提升用户体验。 示例代码

    @Entry
    @Component
    struct TransitionEffectPage {
      @State isShow: boolean = false;
    
      build() {
        Column() {
          Column() {
            if (this.isShow) {
              Image("https://p.qqan.com/up/2024-5/20245101125138293.jpg")
                .width(100)
                .height(100)
                .borderRadius(50)
    
                // 组件内转场
                .transition(
    
                  // 进入和退出使用相同动画
                  // TransitionEffect.OPACITY.animation({duration: 1000})
                  //   .combine(TransitionEffect.rotate({angle: -180}))
                  //   .combine(TransitionEffect.scale({x: 0.1, y: 0.1}))
                  //   .combine(TransitionEffect.move(TransitionEdge.START))
    
                  // 分别设置进入和退出时的动画
                  TransitionEffect.asymmetric(
                    // 进入时的动画
                    TransitionEffect.OPACITY.animation({duration: 1000})
                      .combine(TransitionEffect.rotate({angle: -180}))
                      .combine(TransitionEffect.scale({x: 0.1, y: 0.1}))
                      .combine(TransitionEffect.move(TransitionEdge.START)),
                    // 退出时的动画
                    TransitionEffect.OPACITY.animation({duration: 1000})
                      .combine(TransitionEffect.rotate({angle: 180}))
                      .combine(TransitionEffect.scale({x: 0.1, y: 0.1}))
                      .combine(TransitionEffect.move(TransitionEdge.END))
                  )
                )
            }
          }.height(100)
    
          Button("组件内动画")
            .onClick(() => {
              this.isShow = !this.isShow
            })
        }
        .justifyContent(FlexAlign.Center)
        .width('100%')
        .height('100%')
      }
    }
    

路径动画

设置组件进行位移动画时的运动路径

示例

```
// xxx.ets
@Entry
@Component
struct MotionPathExample {
  @State toggle: boolean = true

  build() {
    Column() {
      Button('click me').margin(50)
        // 执行动画:从起点移动到(300,200),再到(300,500),再到终点
        // path:位移动画的运动路径
        // from:运动路径的起点
        // to:运动路径的终点
        // rotatable:是否跟随路径进行旋转
        .motionPath({ path: 'Mstart.x start.y L300 200 L300 500 Lend.x end.y', from: 0.0, to: 1.0, rotatable: true })
        .onClick(() => {
          animateTo({ duration: 4000, curve: Curve.Linear }, () => {
            this.toggle = !this.toggle // 通过this.toggle变化组件的位置
          })
        })
    }.width('100%').height('100%').alignItems(this.toggle ? HorizontalAlign.Start : HorizontalAlign.Center)
  }
}
```

帧动画

  • 鸿蒙提供帧动画组件来实现逐帧播放图片的能力,可以配置需要播放的图片列表,每张图片可以配置时长

  • 如果用的是资源的地址 。$r("app.media.a") 这个地址不允许动态生成 代码示例

    @Entry
    @Component
    struct ImageAnimatorCase {
      @State state: AnimationStatus = AnimationStatus.Initial
    
      build() {
        Row() {
          Column() {
            ImageAnimator()
             // 图片帧信息集合
              .images([{
                src: '/assets/帧动画/微信-小狗/watch-loadingdog-0@2x.png'
              },{
                src: '/assets/帧动画/微信-小狗/watch-loadingdog-1@2x.png'
              },{
                src: '/assets/帧动画/微信-小狗/watch-loadingdog-2@2x.png'
              },{
                src: '/assets/帧动画/微信-小狗/watch-loadingdog-3@2x.png'
              }])
              // 播放时长,当images中任意一帧图片设置了单独的duration后,该属性设置无效。
              .duration(200)
              // 初始状态,于控制播放状态,默认值:AnimationStatus.Initial
              .state(this.state)
              // 是否从最后一张图片开始播放到第一张图片,默认为false
              .reverse(false)
              // 动画开始前和结束后的状态,默认为Forwards
              // None动画未执行时不会将任何样式应用于目标,动画播放完成之后恢复初始默认状态。
              // Forwards目标将保留动画执行期间最后一个关键帧的状态。
              // Backwards,动画将在应用于目标时立即应用第一个关键帧中定义的值,并在delay期间保留此值。第一个关键帧取决于playMode,playMode为Normal或Alternate时为from的状态,playMode为Reverse或AlternateReverse时为to的状态。
              // Both动画将遵循Forwards和Backwards的规则,从而在两个方向上扩展动画属性。
              .fillMode(FillMode.None)
              // 播放次数
              .iterations(-1)
              // 设置图片大小是否固定为组件大小。 true表示图片大小与组件大小一致,此时设置图片的width 、height 、top 和left属性是无效的。false表示每一张图片的width 、height 、top和left属性都要单独设置。
              // 默认值:true
              .fixedSize(true)
              .width(340)
              .height(240)
             ImageAnimator()
               .images([{
                 src: '/assets/帧动画/小米有品/bigtap_queue_1@3x.png'
               },{
                 src: '/assets/帧动画/小米有品/bigtap_queue_2@3x.png'
               },{
                 src: '/assets/帧动画/小米有品/bigtap_queue_3@3x.png'
               },{
                 src: '/assets/帧动画/小米有品/bigtap_queue_4@3x.png'
               }])
               .duration(200)
               .state(this.state)
               .fillMode(FillMode.None)
               .iterations(-1)
               .width(340)
               .height(240)
             Button("帧动画")
               .onClick(() => {
                 if(this.state === AnimationStatus.Running) {
                   this.state = AnimationStatus.Paused
                   return
                 }
                 this.state = AnimationStatus.Running
               })
          }
          .width('100%')
        }
        .height('100%')
      }
    }
    
  • 帧动画支持的事件

    image.png