动画的原理是在一个时间段内,多次改变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图片解决。因此通过大量的对比验证,确认为鸿蒙底层窜然问题。