在最近的项目开发中,我遇到了需要实现复杂动画效果的需求。在探索解决方案的过程中,我发现了 @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: numbery: numberconstructor(x: number, y: number) {this.x = xthis.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
装饰器的使用方法,并且在实际项目中灵活运用,为用户带来更加丰富的动画体验。