在最近的項目開發(fā)中,我遇到了需要實現(xiàn)復(fù)雜動畫效果的需求。在探索解決方案的過程中,我發(fā)現(xiàn)了 @AnimatableExtend 裝飾器,它為實現(xiàn)動畫效果提供了一種非常靈活且強大的方式。然而,在學(xué)習(xí)這個裝飾器的過程中,我發(fā)現(xiàn)相關(guān)的資料并不是特別豐富,而且很多資料都缺乏系統(tǒng)性的講解。因此,我決定寫這篇博客,將自己的學(xué)習(xí)經(jīng)驗和理解分享出來,希望能幫助更多的開發(fā)者快速掌握 @AnimatableExtend 裝飾器的使用。
1. @AnimatableExtend 裝飾器概述
@AnimatableExtend 裝飾器從 API Version 10 開始支持,為動畫效果的實現(xiàn)提供了一種便捷的方式。從 API version 11 開始,它支持在元服務(wù)中使用。這個裝飾器允許我們自定義動畫屬性,使得我們可以對不同類型的數(shù)據(jù)進行動畫處理。
1.1 裝飾器使用規(guī)則
-
定義位置:
@AnimatableExtend僅支持定義在全局,不支持在組件內(nèi)部定義。 -
參數(shù)類型:
@AnimatableExtend定義的函數(shù)參數(shù)類型必須為number類型或者實現(xiàn)AnimatableArithmetic<T>接口的自定義類型。 -
函數(shù)體限制:
@AnimatableExtend定義的函數(shù)體內(nèi)只能調(diào)用@AnimatableExtend括號內(nèi)組件的屬性方法。
1.2 AnimatableArithmetic<T> 接口說明
AnimatableArithmetic<T> 接口定義了非 number 數(shù)據(jù)類型的動畫運算規(guī)則。對于非 number 類型的數(shù)據(jù)(如數(shù)組、結(jié)構(gòu)體、顏色等)做動畫,需要實現(xiàn)該接口中的加法、減法、乘法和判斷相等函數(shù),使得該數(shù)據(jù)能參與動畫的插值運算和識別該數(shù)據(jù)是否發(fā)生改變。
| 名稱 | 入?yún)㈩愋?/th> | 返回值類型 | 說明 |
|---|---|---|---|
| plus | AnimatableArithmetic<T> | AnimatableArithmetic<T> | 定義該數(shù)據(jù)類型的加法運算規(guī)則 |
| subtract | AnimatableArithmetic<T> | AnimatableArithmetic<T> | 定義該數(shù)據(jù)類型的減法運算規(guī)則 |
| multiply | number | AnimatableArithmetic<T> | 定義該數(shù)據(jù)類型的乘法運算規(guī)則 |
| equals | AnimatableArithmetic<T> | boolean | 定義該數(shù)據(jù)類型的相等判斷規(guī)則 |
2. 使用場景示例
2.1 改變 Text 組件寬度實現(xiàn)逐幀布局效果
下面的示例通過改變 Text 組件的寬度實現(xiàn)逐幀布局的效果。
@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 函數(shù),通過 @AnimatableExtend 裝飾器將其應(yīng)用到 Text 組件上。點擊按鈕時,textWidth 的值會在 100 和 200 之間切換,從而觸發(fā)動畫效果。
2.2 實現(xiàn)折線的動畫效果
為了實現(xiàn)折線的動畫效果,我們需要定義一個自定義類型 Point 和 PointVector,并讓 PointVector 實現(xiàn) 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實現(xiàn)了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 })// 設(shè)置動畫參數(shù)
.size({ height: 250, width: 350 })
.fill(Color.Blue)
.stroke(Color.Yellow)
.backgroundColor('#aaccff')
Button("Play")
.onClick(() => {
// points是實現(xiàn)了可動畫協(xié)議的數(shù)據(jù)類型,points在動畫過程中可按照定義的運算規(guī)則、動畫參數(shù)從之前的PointVector變?yōu)樾碌腜ointVector數(shù)據(jù),產(chǎn)生每一幀的PointVector數(shù)據(jù),進而產(chǎn)生動畫
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 類表示點的數(shù)組。通過實現(xiàn) AnimatableArithmetic<T> 接口,我們?yōu)?PointVector 定義了加法、減法、乘法和相等判斷規(guī)則。然后,我們使用 @AnimatableExtend 裝飾器將 animatablePoints 函數(shù)應(yīng)用到 Polyline 組件上,點擊按鈕時,折線的頂點位置會隨機變化,從而實現(xiàn)動畫效果。
3. 總結(jié)
@AnimatableExtend 裝飾器為實現(xiàn)復(fù)雜動畫效果提供了一種強大而靈活的方式。通過自定義動畫屬性和實現(xiàn) AnimatableArithmetic<T> 接口,我們可以對不同類型的數(shù)據(jù)進行動畫處理。在實際開發(fā)中,我們可以根據(jù)具體需求靈活運用這個裝飾器,實現(xiàn)各種炫酷的動畫效果。希望這篇博客能幫助你快速掌握 @AnimatableExtend 裝飾器的使用,讓你的應(yīng)用更加生動有趣。
通過以上的學(xué)習(xí)和實踐,你可以逐步掌握 @AnimatableExtend 裝飾器的使用方法,并且在實際項目中靈活運用,為用戶帶來更加豐富的動畫體驗。