HarmonyOS 动画一:如何优雅编排控件

756 阅读7分钟

HarmonyOS 动画一:如何优雅编排控件

本文概述:

  • 技术背景:优化用户体验是移动端开发中老生常谈的话题,合理的动画植入可提升控件编排上的美感。
  • 文章大纲:基本动画:属性动画、显示动画、显示动画进阶之路径动画
  • 本文代码链接
  • 动画完整代码链接

属性动画

属性动画概述

  • 属性动画最大的特点:其在使用时,使用链式调用,作为被修饰对象的一个属性

  • 属性动画是什么

    • 在修饰对象的内置属性改变时,使用动画进行过渡。
    • 理论上讲,只要修饰对象的属性存在二值性,即可使用属性动画进行过渡
  • 属性动画有什么作用

    • 常用动画参数:用户可以指定什么

      • 参数:动画持续时间、动画播放速度、动画速率曲线、动画延迟执行时长、动画播放次数、动画播放模式、
      • 回调:在动画播放时触发
  • 我们怎么使用属性动画

    • 确定修饰对象的属性,及属性始末状态
    • 确定修饰作用域,及动画参数
  • 细节问题

    • 尽量不要对颜色使用属性动画过渡,因为实现的效果不固定
    • 在对尺寸进行变化时,需考虑变化前后及过程中对其余布局的影响

属性动画的使用步骤

  • 实际效果:可以用作进度条填充,做一个完整进度的实时动态展示

    Simple001.gif

  • 首先,确定动画的作用对象及相关属性

    • 常用属性:宽高尺寸、摆放状态、控件样式
    • 一般使用@State 修饰控件状态变量,因为其值改变,会自动触发build() 二次执行
    @Entry
    @Component
    struct AttrAnimationPage {
      @State wSize: number = 250//控件宽度
      @State hSize: number = 100//控件高度build() {
        Column() {
          Button('动画调整按钮宽高')
            .margin(30)
            .width(this.wSize)
            .height(this.hSize)
        }.width('100%').margin({top: 20})
      }
    }
    
  • 其次,确定控件的始末状态及触发逻辑

    • 一般使用控件监听,并在监听逻辑中对控件状态变量进行二次赋值,从而确定控件的始末状态
    @Entry
    @Component
    struct AttrAnimationPage {
      ………………
    ​
      build() {
        Column() {
          Button('动画调整按钮宽高')
            .onClick(() => {//确定控件的始末状态及触发逻辑
              if (this.flag) {
                this.wSize = 150
                this.hSize = 60
              } else {
                this.wSize = 250
                this.hSize = 100
              }
              this.flag = !this.flag // 取反
            })
            .margin(30)
            .width(this.wSize)
            .height(this.hSize)
        }.width('100%').margin({top: 20})
      }
    }
    
  • 最后,根据需要,确定属性动画作用域及动画参数

    • 属性动画作用域起点:其修饰的控件内的第一行代码
    • 属性动画作用域起点:.animation({ 的上面一行
    @Entry
    @Component
    struct AttrAnimationPage {
      ………………
    ​
      build() {
        Column() {
          Button('动画调整按钮宽高')
            .onClick(() => {//属性动画作用域起点
              ………………
            })
            .margin(30)
            .width(this.wSize)
            .height(this.hSize)//属性动画作用域终点
            .animation({
              duration: 2000,//动画的持续时间
              curve: Curve.EaseOut, // 动画的速率 默认值Linear,均速
              iterations: -1,//动画重复次数
              playMode: /*PlayMode.Normal 默认值*/ PlayMode.Alternate//保证开启结束都有动画
            })
        }.width('100%').margin({top: 20})
      }
    }
    

属性动画的作用域问题

  • 剔除控件高度动画过渡:可将上述代码改为

    • 注意,此时控件的高度将不再受动画修饰

    • 实际效果:

      Simple2 (2).gif

    @Entry
    @Component
    struct AttrAnimationPage {
      ………………
    ​
      build() {
        Column() {
          Button('动画调整按钮宽度')
            .onClick(() => {//属性动画作用域起点
              ………………
            })
            .margin(30)
            .backgroundColor(this.btnBackColor)
            .width(this.wSize)//属性动画作用域终点
            .animation({
              …………
            })
            
            .height(this.hSize)//此时,按钮的高度不再被动画所修饰
            
        }.width('100%').margin({top: 20})
      }
    }
    
  • 多作用域相互覆盖

    • 该动画最终持续时间仍为500ms,虽然说,后面的动画作用域看起来会掩盖前面。但是,属性动画的最终效果遵从就近原则

    • 实际效果:动画的持续时间只有500ms

      simple3.gif

    Button('作用域覆盖')
        .onClick(() => {
          this.rotateAngle = 90
        })
        .margin(30)
        .rotate({angle: this.rotateAngle})
        .animation({
          duration: 500,
          curve: Curve.Friction, // 动画的速率,刚开始很快,快结束变慢
          iterations: -1, // 设置-1表示动画无限执行循环
          playMode: PlayMode.Alternate//保证开始结束都有动画
        })
        .animation({
          duration: 2000,
        })
        .animation({
          duration: 4000,
        })
        .animation({
          duration: 6000,
        })
        .animation({
          duration: 10000,
        })
    

显示动画

显示动画概述

  • 显示动画的最大特点:使用animateTo ({},()=>{}) 的形式

  • 显示动画有什么作用

    • 常用动画参数:用户可以指定什么

      • 参数:动画持续时间、动画播放速度、动画速率曲线、动画延迟执行时长、动画播放次数、动画播放模式、
      • 回调:在动画播放时触发
  • 我们怎么使用显示动画:核心在于确定两个参数

    • 确定代码位置:明确显示动画触发时机
    • 第一个参数:显示动画的基本属性及回调逻辑
    • 第二个参数:动画启动后UI 属性的更新逻辑
  • 细节问题

    • 显示动画对代码的侵入性较高,应优先考虑属性动画

显示动画使用步骤

  • 实现效果:点击后动态调整宽高,且自动触发动画播放结束回调 simple4.gif
  • 首先,确定动画的作用对象及相关属性

    • 常用属性:宽高尺寸、摆放状态、控件样式
    • 一般使用@State 修饰控件状态变量,因为其值改变,会自动触发build() 二次执行
    @Entry
    @Component
    struct AnimateToPage {
      @State wSize: number = 250//控件宽度
      @State hSize: number = 100//控件高度build() {
        Column() {
          Button('动态调整按钮宽高')
            .margin(30)
            .width(this.wSize)
            .height(this.hSize)
            .onClick(() => {
                ………………
            }
        }.width('100%').margin({top: 20})
      }
    }
    
  • 其次,确定控件的始末状态及触发逻辑

    • 核心在于补全两个参数

      animateTo({},() => {})
      
    @Entry
    @Component
    struct AnimateToPage {
      @State flag: boolean = true
      ………………
    ​
      @State rotateAngle: number = 0build() {
        Column() {
          Button('动态调整按钮宽高')
            .margin(30)
            .width(this.wSize)
            .height(this.hSize)
            
            .onClick(() => {
              if (this.flag) {
                animateTo({//配置显示动画参数,用于动画过渡
                  duration: 2000,
                  curve: Curve.Friction,
                  onFinish: () => {//显示动画结束后回调
                    promptAction.showToast({message: '动画1执行结束了'})
                  }
                }, () => {//显示动画修饰对象的结束状态
                  this.wSize = 150
                  this.hSize = 60
                })
              } else {
                animateTo({}, () => {
                  this.wSize = 250
                  this.hSize = 100
                })
              }
              this.flag = !this.flag//监听标志为取反,支持重复点击
            })
        }.width('100%').margin({top: 20})
      }
    }
    

显示动画实际使用实例

  • 点击按钮后实现图片动态隐藏及展示

    • 文本框表示当前图像状态,
    • 隐藏逻辑:先旋转再隐藏
    • 显示逻辑:有明显展开 simple5.gif
    import promptAction from '@ohos.promptAction'
    @Entry
    @Component
    struct AnimateToPage {
    
      @State flag: boolean = true
      @State show: string = 'show'
    
      build() {
        Column() {
          Button(this.show).width(88).height(38).margin(30)
            .onClick(() => {
              animateTo({duration: 2000}, () => {
                // 点击Button的时候 控制 下面的Image的显示与隐藏
                if (this.flag) {
                  this.show = 'hide'
                } else {
                  this.show = 'show'
                }
                this.flag = !this.flag
              })
            })
          if (this.flag) {
            // Image的显示与隐藏 配置不同的 过度效果
            Image($r('app.media.app_icon')).width(300).height(300)
              .transition({type: TransitionType.Insert, scale: {x: 0, y: 1.0}}) // Image出现显示的时候, 渐变缩放的效果
              .transition({type: TransitionType.Delete, rotate: {angle: 180}}) // Image出现隐藏的时候, 选择180°
          }
        }
        .width('100%').margin({top: 20})
      }
    }
    

路径动画

路径动画概述

  • 概述:路径动画属于显示动画的一种,但此动画不常用,仅做补充
  • 修饰对象:一般用于修饰系统控件,类似按钮等

路径动画使用步骤

  • 设置组件的运动路径。

    • path:位移动画的运动路径,使用svg路径字符串。path中支持使用start和end进行起点和终点的替代,如:'Mstart.x start.y L50 50 Lend.x end.y Z',更多说明请参考绘制路径
    • from:运动路径的起点。取值范围:[0, 1]
    • to:运动路径的终点。取值范围:[0, 1]
    • rotatable:是否跟随路径进行旋转。

路径动画使用实例

  • 点击按钮之后,可观察按钮在水平方向上进行动态移动

  • 实际效果:

    simple6.gif

/*路径动画*/
@Entry
@Component
struct PathStudy {
  @State value: boolean = true
​
  build() {
    Column() {
      Button("PathAnimation OK").margin(50)
        // 从 起点移动到 (300/200), 紧接着 再次移动到 (300/500)
        .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: 6000, curve: Curve.Friction}, () => {
            this.value = !this.value // 通过this.value改变组件的位置
          })
        })
    }.width('100%').height('100%').alignItems(this.value ? HorizontalAlign.Start : HorizontalAlign.Center)
  }
}