在最近的项目开发中,我遇到了需要实现复杂动画效果的需求。在探索解决方案的过程中,我发现了 @AnimatableExtend
装饰器,它为实现动画效果提供了一种非常灵活且强大的方式。然而,在学习这个装饰器的过程中,我发现相关的资料并不是特别丰富,而且很多资料都缺乏系统性的讲解。因此,我决定写这篇博客,将自己的学习经验和理解分享出来,希望能帮助更多的开发者快速掌握 @AnimatableExtend
装饰器的使用。
1. @AnimatableExtend
装饰器概述
@AnimatableExtend
装饰器从 API Version 10 开始支持,为动画效果的实现提供了一种便捷的方式。从 API version 11 开始,它支持在元服务中使用。这个装饰器允许我们自定义动画属性,使得我们可以对不同类型的数据进行动画处理。
1.1 装饰器使用规则
- 定义位置:
@AnimatableExtend
仅支持定义在全局,不支持在组件内部定义。 - 参数类型:
@AnimatableExtend
定义的函数参数类型必须为number
类型或者实现AnimatableArithmetic<T>
接口的自定义类型。 - 函数体限制:
@AnimatableExtend
定义的函数体内只能调用@AnimatableExtend
括号内组件的属性方法。
1.2 AnimatableArithmetic<T>
接口说明
AnimatableArithmetic<T>
接口定义了非 number
数据类型的动画运算规则。对于非 number
类型的数据(如数组、结构体、颜色等)做动画,需要实现该接口中的加法、减法、乘法和判断相等函数,使得该数据能参与动画的插值运算和识别该数据是否发生改变。
名称 | 入参类型 | 返回值类型 | 说明 |
---|---|---|---|
plus | AnimatableArithmetic<T> | AnimatableArithmetic<T> | 定义该数据类型的加法运算规则 |
subtract | AnimatableArithmetic<T> | AnimatableArithmetic<T> | 定义该数据类型的减法运算规则 |
multiply | number | AnimatableArithmetic<T> | 定义该数据类型的乘法运算规则 |
equals | AnimatableArithmetic<T> | boolean | 定义该数据类型的相等判断规则 |
2. 使用场景示例
2.1 改变 Text 组件宽度实现逐帧布局效果
下面的示例通过改变 Text
组件的宽度实现逐帧布局的效果。
@AnimatableExtend(Text)
function animatableWidth(width: number) {
.width(width)
}
@Entry
@Component
struct AnimatablePropertyExample {
@State textWidth: number = 100;
build() {
Column() {
Text("AnimatableProperty")
.animatableWidth(this.textWidth)
.animation({ duration: 3000, curve: Curve.EaseInOut })
Button("Play")
.onClick(() => {
this.textWidth = this.textWidth == 100 ? 200 : 100;
})
}.width("100%")
.padding(20)
}
}
在这个示例中,我们定义了一个 animatableWidth
函数,通过 @AnimatableExtend
装饰器将其应用到 Text
组件上。点击按钮时,textWidth
的值会在 100 和 200 之间切换,从而触发动画效果。
2.2 实现折线的动画效果
为了实现折线的动画效果,我们需要定义一个自定义类型 Point
和 PointVector
,并让 PointVector
实现 AnimatableArithmetic<T>
接口。
class Point {
x: number
y: number
constructor(x: number, y: number) {
this.x = x
this.y = y
}
plus(rhs: Point): Point {
return new Point(this.x + rhs.x, this.y + rhs.y)
}
subtract(rhs: Point): Point {
return new Point(this.x - rhs.x, this.y - rhs.y)
}
multiply(scale: number): Point {
return new Point(this.x * scale, this.y * scale)
}
equals(rhs: Point): boolean {
return this.x === rhs.x && this.y === rhs.y
}
}
// PointVector实现了AnimatableArithmetic<T>接口
class PointVector extends Array<Point> implements AnimatableArithmetic<PointVector> {
constructor(value: Array<Point>) {
super();
value.forEach(p => this.push(p))
}
plus(rhs: PointVector): PointVector {
let result = new PointVector([])
const len = Math.min(this.length, rhs.length)
for (let i = 0; i < len; i++) {
result.push((this as Array<Point>)[i].plus((rhs as Array<Point>)[i]))
}
return result
}
subtract(rhs: PointVector): PointVector {
let result = new PointVector([])
const len = Math.min(this.length, rhs.length)
for (let i = 0; i < len; i++) {
result.push((this as Array<Point>)[i].subtract((rhs as Array<Point>)[i]))
}
return result
}
multiply(scale: number): PointVector {
let result = new PointVector([])
for (let i = 0; i < this.length; i++) {
result.push((this as Array<Point>)[i].multiply(scale))
}
return result
}
equals(rhs: PointVector): boolean {
if (this.length != rhs.length) {
return false
}
for (let i = 0; i < this.length; i++) {
if (!(this as Array<Point>)[i].equals((rhs as Array<Point>)[i])) {
return false
}
}
return true
}
get(): Array<Object[]> {
let result: Array<Object[]> = []
this.forEach(p => result.push([p.x, p.y]))
return result
}
}
@AnimatableExtend(Polyline)
function animatablePoints(points: PointVector) {
.points(points.get())
}
@Entry
@Component
struct AnimatablePropertyExample {
@State points: PointVector = new PointVector([
new Point(30, Math.random() * 250),
new Point(80, Math.random() * 250),
new Point(130, Math.random() * 250),
new Point(180, Math.random() * 250),
new Point(230, Math.random() * 250),
])
build() {
Column() {
Polyline()
.animatablePoints(this.points)
.animation({ duration: 1500, curve: Curve.EaseOut })// 设置动画参数
.size({ height: 250, width: 350 })
.fill(Color.Blue)
.stroke(Color.Yellow)
.backgroundColor('#aaccff')
Button("Play")
.onClick(() => {
// points是实现了可动画协议的数据类型,points在动画过程中可按照定义的运算规则、动画参数从之前的PointVector变为新的PointVector数据,产生每一帧的PointVector数据,进而产生动画
this.points = new PointVector([
new Point(30, Math.random() * 250),
new Point(80, Math.random() * 250),
new Point(130, Math.random() * 250),
new Point(180, Math.random() * 250),
new Point(230, Math.random() * 250),
])
})
}.width("100%")
.padding(20)
}
}
在这个示例中,我们定义了 Point
类表示二维平面上的点,PointVector
类表示点的数组。通过实现 AnimatableArithmetic<T>
接口,我们为 PointVector
定义了加法、减法、乘法和相等判断规则。然后,我们使用 @AnimatableExtend
装饰器将 animatablePoints
函数应用到 Polyline
组件上,点击按钮时,折线的顶点位置会随机变化,从而实现动画效果。
3. 总结
@AnimatableExtend
装饰器为实现复杂动画效果提供了一种强大而灵活的方式。通过自定义动画属性和实现 AnimatableArithmetic<T>
接口,我们可以对不同类型的数据进行动画处理。在实际开发中,我们可以根据具体需求灵活运用这个装饰器,实现各种炫酷的动画效果。希望这篇博客能帮助你快速掌握 @AnimatableExtend
装饰器的使用,让你的应用更加生动有趣。
通过以上的学习和实践,你可以逐步掌握 @AnimatableExtend
装饰器的使用方法,并且在实际项目中灵活运用,为用户带来更加丰富的动画体验。