Skip to content

@AnimatableExtend 装饰器:定义可动画属性

@AnimatableExtend 装饰器用于自定义可动画的属性方法 在这个属性方法中修改组件不可动画的属性。在动画执行过程时,通过逐帧回调函数修改不可动画属性值,让不可动画属性也能实现动画效果。 也可通过逐帧回调函数每帧修改可动画属性的值,实现逐帧布局的效果。

  • 可动画属性:如果一个属性方法在 animation 属性前调用,改变这个属性的值可以生效 animation 属性的动画效果,这个属性称为可动画属性。比如 height、width、backgroundColor、translate 属性,Text 组件的 fontSize 属性等。

  • 不可动画属性:如果一个属性方法在 animation 属性前调用,改变这个属性的值不能生效 animation 属性的动画效果,这个属性称为不可动画属性。比如 Polyline 组件的 points 属性等。

装饰器使用说明

语法

bash
@AnimatableExtend(UIComponentName) function functionName(value: typeName) {
  .propertyName(value)
}
  • @AnimatableExtend 仅支持定义在全局,不支持在组件内部定义

  • @AnimatableExtend定义的函数参数类型必须为 number 类型或者实现 AnimtableArithmetic<T>接口的自定义类型。

  • @AnimatableExtend定义的函数体内只能调用@AnimatableExtend括号内组件的属性方法

AnimtableArithmetic<T>接口说明

对复杂数据类型做动画,需要实现 AnimtableArithmetic<T>接口中加法、减法、乘法和判断相等函数。

  • plus
bash

1. 入参类型: `AnimtableArithmetic<T>`;

2. 返回值类型: `AnimtableArithmetic<T>`;

3. 加法函数
  • subtract
bash

1. 入参类型: `AnimtableArithmetic<T>`

2. 返回值类型: `AnimtableArithmetic<T>`

3. 减法函数
  • multiply
bash

1. 入参类型: number

2. 返回值类型: `AnimtableArithmetic<T>`

3. 乘法函数
  • equals
bash

1. 入参类型: `AnimtableArithmetic<T>`

2. 返回值类型: boolean

3. 相等判断函数

使用场景

以下示例通过改变 Text 组件宽度实现逐帧布局的效果。

bash

@AnimatableExtend(Text)
function animatableWidth(width: number) {
  .width(width)
}

@Entry
@Component
struct AnimatablePropertyExample {
  @State textWidth: number = 80;

  build() {
    Column() {
      Text("AnimatableProperty")
        .animatableWidth(this.textWidth)
        .animation({ duration: 2000, curve: Curve.Ease })
      Button("Play")
        .onClick(() => {
          this.textWidth = this.textWidth == 80 ? 160 : 80;
        })
    }.width("100%")
    .padding(10)
  }
}

图四动画样式

一下示例实现折现的动画效果

bash
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
  }
}

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(50, Math.random() * 200),
    new Point(100, Math.random() * 200),
    new Point(150, Math.random() * 200),
    new Point(200, Math.random() * 200),
    new Point(250, Math.random() * 200),
  ])
  build() {
    Column() {
      Polyline()
        .animatablePoints(this.points)
        .animation({duration: 1000, curve: Curve.Ease})
        .size({height:220, width:300})
        .fill(Color.Green)
        .stroke(Color.Red)
        .backgroundColor('#eeaacc')
      Button("Play")
        .onClick(() => {
          this.points = new PointVector([
            new Point(50, Math.random() * 200),
            new Point(100, Math.random() * 200),
            new Point(150, Math.random() * 200),
            new Point(200, Math.random() * 200),
            new Point(250, Math.random() * 200),
          ])
        })
    }.width("100%")
    .padding(10)
  }
}

图5