【HarmonyOS开发】拖拽动画的实现

动画的原理是在一个时间段内,多次改变UI外观,由于人眼会产生视觉暂留,所以最终看到的就是一个“连续”的动画。UI的一次改变称为一个动画帧,对应一次屏幕刷新,而决定动画流畅度的一个重要指标就是帧率FPS(Frame Per Second),即每秒的动画帧数,帧率越高则动画就会越流畅。
ArkUI中,产生动画的方式是改变属性值且指定动画参数。动画参数包含了如动画时长、变化规律(即曲线)等参数。当属性值发生变化后,按照动画参数,从原来的状态过渡到新的状态,即形成一个动画。

1、动画分类

输入图片说明

2、常见动画的使用

通过改变元素的宽高、位置、布局等触发动画
官方文档-动画

// 显式动画 https://developer.harmonyos.com/cn/docs/documentation/doc-references/ts-explicit-animation-0000001281480722
animateTo(value: AnimateParam, event: () => void): void
// 属性动画 https://developer.harmonyos.com/cn/docs/documentation/doc-references/ts-animatorproperty-0000001333321185 
animation(value: AnimateParam)
// 转场动画-必须和animateTo配合使用 https://developer.harmonyos.com/cn/docs/documentation/doc-references/ts-page-transition-animation-0000001281201178
transition(value: TransitionOptions)
transition({ type: TransitionType.All, scale: { x: 0, y: 0 } }) // 全部使用一种动画
transition({ type: TransitionType.Insert, translate: { x: 200, y: -200 }, opacity: 0 }) // 进入/插入动画
transition({ type: TransitionType.Delete, rotate: { x: 0, y: 0, z: 1, angle: 360 } }) // 移出/删除动画

3、矩阵动画的使用

这一块我们重点关注几个常用的属性

3.1 translate(拖拽动画实现的主要属性)

translate({x?: number, y?: number, z?: number}): Object

当x,y坐标为0时,则意味着,每个元素按照各自的位置进行排列(例如:grid、list、Stack等)。
因此,我们可以根据元素的下标index,通过一些算法来改变坐标的位置,从而实现拖拽动画,具体见代码

DOM的实现

// isStartDrag 为是否开始拖拽,当开始拖拽时,我们动态改变矩阵中元素的坐标
Grid() {
    ForEach(this.selected, (item: dataListType) => {
        GridItem() {
            Text(item.text).blockTextStyle(this.blockWidth)
        }
        .translate({
            x: this.isStartDrag ? this.geyCoodXY(index).x : 0,
            y: this.isStartDrag? this.geyCoodXY(index).y : 0
        })
        .animation({
            duration: DURATION, // 动画时长
            curve: Curve.Linear, // 动画曲线
            iterations: 1, // 播放次数
            playMode: PlayMode.Normal // 动画模式
        })
    })
}
.columnsTemplate('1fr 1fr 1fr')
.columnsGap(16)
.rowsGap(16)
.editMode(true)  //设置Grid是否进入编辑模式,进入编辑模式可以拖拽Grid组件内部GridItem
.supportAnimation(true) // 开启动画

坐标改变的算法

  geyCoodXY(index) {
    const gridCol = this.getGridCol()
    let x = 0
    let y = 0
    if(this.insterIndex != -1) {
      if(index >= this.insterIndex) {
        // 判断是否需要换行
        // 需要取余如果等于0,则需要换行,需要进行下移和左移
        if(parseInt(((index) % gridCol).toString()) === gridCol - 1) {
          // 判断是否为当前列的最后一个
          if(this.options.type === 'object') {
            x = x - this.blockWidth * (gridCol - 1) - 16 * (gridCol - 2) - 19
            y = y + this.blockHeight - 16.5
          } else {
            x = x - this.blockWidth * (gridCol - 1) - 16 * (gridCol - 2) - 13
            y = y + this.blockHeight + 18
          }
        } else {
          // 默认右移
          x = x + 16 + this.blockWidth + 1
        }
      }

      if(!this.isStartDrag) {
        x = 0
        y = 0
      }
    }
    return {
      x: x,
      y: y
    }
  }

  /**
   * 获取Grid高度计算是否需要+1
   *     场景1:当前数组(data)长度小于列(colNum)的长度
   *     场景2:当前数组的长度等于拖拽前的长度 && 对数组长度%列长度区域不为0
   * */
  getGridNum(data) {
    let len = data.length
    let num = 0
    if(len < this.colNum) {
      num = 1
    }
    if(parseInt((len % this.colNum).toString()) !== 0 && this.editGridDataLength === len) {
      num = 1
    }
    return num
  }

  /**
   * 获取当前布局列数
   * 默认:文本COL_TEXT:4
   * 默认:图文COL_IMAGE_TEXT:3
   * */
  getGridCol() {
    return this.options.type === 'object' ? COL_IMAGE_TEXT : COL_TEXT
  }

3.2 scale

缩放函数,配合transform进行使用

scale({x?: number, y?: number, z?: number, centerX?: number, centerY?: number}): Object

3.3 rotate

旋转函数

rotate({x?: number, y?: number, z?: number, angle?: number, centerX?: Length, centerY?: Length}): Object

3.4 transformPoint

坐标映射,可以将当前的变换效果作用到一个坐标点上。

源码地址:MeshObjectEdit

输入图片说明

4、 注意点

4.1 Grid布局中的Item使用属性动画时,只能使用自带的curve,无法自定义

4.2 底层渲染问题

 在开发拖拽动画时,发现png的图片在拖拽结束后,会出现图片闪动的不流畅问题,改为svg图片解决。因此通过大量的对比验证,确认为鸿蒙底层窜然问题。

猜你喜欢

转载自blog.csdn.net/qq_23334071/article/details/134499348