前言
本文基于Api13
这两天在搞有关动画相关的,有一个很简单的平移动画,却卡了我一段时间,由于事情曲折,各位友友听我娓娓道来;说的是,有一个组件,设置了translate属性,让其从左到右平移,为了能够平缓的平移,需要加上平移时间,代码如下:
@Entry
@Component
struct Index {
@State translateX: number = 0
build() {
Column() {
Text("动画" )
.width(50)
.height(50)
.backgroundColor(Color.Pink)
.textAlign(TextAlign.Center)
.margin({ top: 10 })
.fontColor(Color.White)
.translate({ x: this.translateX })
.animation({ duration: 500 })
Button("点击")
.margin({ top: 10 })
.onClick(() => {
this.translateX = 200
})
}
}
}
以上呢,就是一段很简单的代码,点击按钮之后,让组件由左向右平移200,持续动画时间为500毫秒。
我们看下实际的运行效果:
可以看到,上述的代码,一切都没问题的,都按照正常的功能执行,但是,我不是移动一个组件,而是多个组件,于是,我又创建了一个组件。
Column() {
Text("动画1")
.width(50)
.height(50)
.backgroundColor(Color.Pink)
.textAlign(TextAlign.Center)
.margin({ top: 10 })
.fontColor(Color.White)
.translate({ x: this.translateX })
.animation({ duration: 500 })
Text("动画2")
.width(50)
.height(50)
.backgroundColor(Color.Pink)
.textAlign(TextAlign.Center)
.margin({ top: 10 })
.fontColor(Color.White)
.translate({ x: this.translateX })
.animation({ duration: 500 })
Button("点击")
.margin({ top: 10 })
.onClick(() => {
this.translateX = 200
})
}
以上的代码运行之后,也是没有问题,如果有N个组件呢,一个一个复制也不是办法,于是,我就使用ForEach来遍历,这样直接控制数据源就行了,代码又改为了如下:
Column() {
ForEach([1, 2], (item: number, index: number) => {
Text("动画"+index)
.width(50)
.height(50)
.backgroundColor(Color.Pink)
.textAlign(TextAlign.Center)
.margin({ top: 10 })
.fontColor(Color.White)
.translate({ x: this.translateX })
.animation({ duration: 500 })
})
Button("点击")
.margin({ top: 10 })
.onClick(() => {
this.translateX = 200
})
}
效果和之前的一样,点击按钮之后,两个组件也能同步的进行平移,有的友友就说了,你的问题在哪?tell me why?looking my eyes!
各位友友稍安勿躁,问题马上来了。
因为牵扯的不仅仅是多组件移动的问题,后续的功能,还有每个组件的移动距离是不一样的,于是,我又重新定义的数据源,而移动距离则从数据源中获取:
ForEach(this.translateArray, (item: number,index:number) => {
Text("动画"+index)
.width(50)
.height(50)
.backgroundColor(Color.Pink)
.textAlign(TextAlign.Center)
.margin({ top: 10 })
.fontColor(Color.White)
.translate({ x: item })
.animation({ duration: 500 })
})
以上的代码,就能实现每个组件的平移距离动态设置,想让那个组件移动,就让哪个组件移动,只需要控制数据源即可,貌似代码非常完美,当时我也是这么觉得,于是就运行了程序。
让第一个组件平移200,让第二个组件平移300。
Button("点击")
.margin({ top: 10 })
.onClick(() => {
this.translateArray[0] = 200
this.translateArray[1] = 300
})
运行之后看下效果:
移动过去了吗?哎,确实移动过去了,但是,平移动画的时间没了!只是很生硬的一下平移过去了,一个ForEach把动画时间干没了,此时的我,大脑早已一串问号。
追寻问题
从前言中,可以看到,一开始使用ForEach,只是数据源固定的,使用共同的平移属性,那时的代码运行之后,还一切正常,直到数据源切换为了动态的数据源,此时的动画时间就不生效了;针对这个问题,我们再次做下验证,让数据源和改变的平移数据区分开来,我们再次看下实际的效果。
@Entry
@Component
struct Index {
@State dataArray: number[] = [0, 0]
@State translateArray: number[] = [0, 0]
build() {
Column() {
ForEach(this.dataArray, (_: number,index:number) => {
Text("动画"+index)
.width(50)
.height(50)
.backgroundColor(Color.Pink)
.textAlign(TextAlign.Center)
.margin({ top: 10 })
.fontColor(Color.White)
.translate({ x: this.translateArray[index] })
.animation({ duration: 500 })
})
Button("点击")
.margin({ top: 10 })
.onClick(() => {
this.translateArray[0] = 200
this.translateArray[1] = 300
})
}
}
}
代码还是无比的简单,定义了两个数据源,一个用于数据加载,一个用于平移距离设置,运行之后,我们看下效果:
我们可以发现,动画时间又生效了,基本上,我们就可以暂时得出结论了,在ForEach中的属性动画,如果数据源发生改变,那么动画时间是不生效的。
这是为什么呢?
数据源发生变化,和动画时间有什么关系?这简直就是风牛马不相及,于是,我就查看了官网的ForEach介绍,看到了这样的一段话:
似乎恍然大悟,ForEach中当键值变化时,ArkUI框架将视为该数组元素已被替换或修改,并会基于新的键值创建一个新的组件。
查看了之前的代码,由于没有设置,都是统一的使用的默认的键值,也就是(item: Object, index: number) => { return index + '__' + JSON.stringify(item); },打印了一下日志,确实发生了变化。
不过有一事,仍然不明,你组件创建创建呗,和我动画时间有毛关系,接着又查看了官方对animation的相关介绍。
如果内部组件还未创建,动画时机过早,动画属性没有初值无法对组件产生动画。
通过以上,我们就能很直观的明白了问题的原因,第一个,由于键值发生了变化,造成了组件重新创建,第二个,由于组件重新创建,动画时机过早,造成属性未生效。
相关总结
这是一个由属性动画造成的一系列问题,折射出了ForEach的键值知识点,所以啊,友友们,遇到问题,莫慌,还是要以官方为主,不过此问题,还是给自己上了一课,基础知识不牢,是最大的痛点!
有的友友说了,虽然,采用两个数据源,解决了以上的问题,可我就想使用一个呢,这个,在实际的开发中,可以使用对象数组的形式,更改需要变化的属性即可,避免重新创建新的组件。
具体案例如下:
@Observed
class TranslateBean {
name?: string
translate?: number
constructor(name: string, translate: number) {
this.name = name
this.translate = translate
}
}
@Component
struct TextView {
@ObjectLink item: TranslateBean
build() {
Text(this.item.name)
.width(50)
.height(50)
.backgroundColor(Color.Pink)
.textAlign(TextAlign.Center)
.margin({ top: 10 })
.fontColor(Color.White)
.translate({ x: this.item.translate })
.animation({ duration: 500 })
}
}
@Entry
@Component
struct Index {
@State dataArray: TranslateBean[] = [new TranslateBean("动画一", 0), new TranslateBean("动画二", 0)]
@State translateArray: number[] = [200, 200]
build() {
Column() {
ForEach(this.dataArray, (item: TranslateBean, index: number) => {
TextView({ item: item })
})
Button("点击")
.margin({ top: 10 })
.onClick(() => {
this.dataArray[0].translate = 200
this.dataArray[1].translate = 300
})
}
}
}