本文介绍鸿蒙中的装饰器的作用
@struct
自定义组件基于struct实现,struct + 自定义组件名 + {…}的组合构成自定义组件,不能有继承关系。对于struct的实例化,可以省略new。
@Component
@Component装饰器仅能装饰struct关键字声明的数据结构。struct被@Component装饰后具备组件化的能力,需要实现build方法描述UI,一个struct只能被一个@Component装饰。@Component可以接受一个可选的bool类型参数。
@Component({ freezeWhenInactive: true })
是否开启组件冻结,
- 启用后,系统将仅对处于激活状态的自定义组件进行更新,这使得UI框架可以尽量缩小更新范围,仅限于用户可见范围内(激活状态)的自定义组件,从而提高复杂UI场景下的刷新效率。
- 当之前处于inactive状态的自定义组件重新变为active状态时,状态管理框架会对其执行必要的刷新操作,确保UI的正确展示。
其实就是优化界面UI刷新的性能。就是只刷新当前可见的自定义组件,对于不可见的自定义组件的刷新会延迟到他们可见时。
注意,组件active/inactive并不等同于肉眼可见,而是他们在不在屏幕内 - 页面路由:当前栈顶页面为active,非栈顶不可见页面为inactive。
TabContent:只有当前显示的TabContent中的自定义组件处于active状态,其余则为inactive。- LazyForEach:仅当前显示的LazyForEach中的自定义组件为active,而缓存节点的组件则为inactive。
Navigation:当前显示的NavDestination中的自定义组件为active,而其他未显示的NavDestination组件则为inactive。
像那种堆叠情况下被遮住的组件,尽管肉眼看不见,但是他们并不被视为inactive状态,因此不在组件冻结的适用范围内。
@State
表示组件中的状态变量,状态变量变化会触发UI刷新。
@Builder
@Builder装饰的函数也称为“自定义构建函数”。他是ArkUI提供了一种轻量的UI元素复用机制,该自定义组件内部UI结构固定,仅与使用方进行数据传递,开发者可以将重复使用的UI元素抽象成一个方法,在build方法里调用。
限制条件
- @Builder通过按引用传递的方式传入参数,才会触发动态渲染UI,并且参数只能是一个。
- @Builder如果传入的参数是两个或两个以上,不会触发动态渲染UI。
- @Builder传入的参数中同时包含按值传递和按引用传递两种方式,不会触发动态渲染UI。
- @Builder的参数必须按照对象字面量的形式,把所需要的属性一一传入,才会触发动态渲染UI。
使用说明
私有自定义构建函数
@Builder MyBuilderFunction() {}
this.MyBuilderFunction()
- 允许在自定义组件内定义一个或多个@Builder方法,该方法被认为是该组件的私有、特殊类型的成员函数。
- 私有自定义构建函数允许在自定义组件内、build方法和其他自定义构建函数中调用。
- 在自定义函数体中,this指代当前所属组件,组件的状态变量可以在自定义构建函数内访问。建议通过this访问自定义组件的状态变量而不是参数传递。
全局自定义构建函数
@Builder function MyGlobalBuilderFunction() { ... }
MyGlobalBuilderFunction()
- 如果不涉及组件状态变化,建议使用全局的自定义构建方法。
- 全局自定义构建函数允许在build方法和其他自定义构建函数中调用。
参数传递规则
参数传递有值传递和引用传递
- 参数的类型必须与参数声明的类型一致,不允许undefined、null和返回undefined、null的表达式。
- 在@Builder修饰的函数内部,不允许改变参数值。
- @Builder内UI语法遵循UI语法规则。
- 只有传入一个参数,且参数需要直接传入对象字面量才会按引用传递该参数,其余传递方式均为按值传递。
实例演示
按引用传递参数
@Entry
@Component
struct BuilderTest {
@State label: string = 'Hello';
build() {
Column() {
overBuilder({ paramA1: this.label })
Button('Click me').onClick(() => {
this.label = 'ArkUI';
})
}
}}
class Tmp {
paramA1: string = ''
}
@Builder
function overBuilder(params: Tmp) {
Row() {
Text(`显示文本: ${params.paramA1} `)
}
}
打开页面展示:
当点击后显示:
可以看到文本已经改变了
当按引用传递参数时,如果在@Builder方法内调用自定义组件,ArkUI提供 $$作为按引用传递参数的范式。(这里没明白是什么意思,正常的命名不是也可以吗,$$ 在这里又是什么意思呢)
@Builder
function overBuilder($$: Tmp) {
Row() {
Text(`显示文本: ${$$.paramA1} `)
}
}
按值传递参数
调用@Builder装饰的函数默认按值传递。当传递的参数为状态变量时,状态变量的改变不会引起@Builder方法内的UI刷新。
@Entry
@Component
struct BuilderTest {
@State label: string = 'Hello';
build() {
Column() {
overBuilder2(this.label)
Button('Click me').onClick(() => {
this.label = 'ArkUI';
})
}
}}
@Builder
function overBuilder2(paramA1: string) {
Row() {
Text(`值传递显示文本: ${paramA1} `)
}
}
以上代码运行,点击按钮并不会导致界面内容文本的改变。如果想要刷新UI可以通过在@ComponentV2装饰器修饰的自定义组件里配合使用@ObservedV2和@Trace装饰器来实现
@ObservedV2
class ParamTmp {
@Trace count: number = 0;
}
@Entry
@ComponentV2
struct PageBuilder {
@Local builderParams: ParamTmp = new ParamTmp();
@Local count: number = 0;
private progressTimer: number = -1;
aboutToAppear(): void {
this.progressTimer = setInterval(() => {
if (this.builderParams.count < 100) {
this.builderParams.count += 5;
this.count = this.count + 1;
} else {
clearInterval(this.progressTimer)
}
}, 500);
}
build() {
Column() {
Text(`count = ${this.count}`)
Text(`builderParams :${this.builderParams.count}`)
}
.width('100%')
.height('100%')
}
}
文档中demo很复杂,差点没明白过来,上面是一个简单的demo,运行上面的代码会发现文本会变化,不管把哪里的 @Local去掉后,对应的文本就不会变了。
[!warning] 注意
当参数存在两个或者两个以上的时候,就算通过对象字面量的形式传递,值的改变也不会引起UI刷新。所以我们需要保证@Builder装饰器对用的方法只有一个参数。
[!warning] 注意
在@Builder函数里使用if判断语句时,创建的组件没有被Column/Row(根节点)包裹,会出现组件创建不出来的情况。
const showComponent: boolean = true;
@Builder function OverlayNode() {
// 没有Column或者Row根节点导致Text组件没有创建
if (showComponent) {
Text("This is overlayNode Blue page")
.fontSize(20)
.fontColor(Color.Blue)
.height(100)
.textAlign(TextAlign.End)
} else {
Text("This is overlayNode Red page")
.fontSize(20)
.fontColor(Color.Red)
}
}
@Entry
@Component
struct OverlayExample {
build() {
RelativeContainer() {
Text('Hello World')
.overlay(OverlayNode(), { align: Alignment.Center})
}
.height('100%')
.width('100%')
}
}
@LocalBuilder
官网上介绍是:在使用@Builder做引用数据传递时,会考虑组件的父子关系,使用了bind(this)之后,组件的父子关系和状态管理的父子关系并不一致。为了解决组件的父子关系和状态管理的父子关系保持一致,引入@LocalBuilder装饰器。@LocalBuilder拥有和局部@Builder相同的功能,且比局部@Builder能够更好的确定组件的父子关系和状态管理的父子关系。
使用说明
@LocalBuilder MyBuilderFunction() { ... }
this.MyBuilderFunction()
- 允许在自定义组件内定义一个或多个@LocalBuilder方法,该方法被认为是该组件的私有、特殊类型的成员函数。
- 自定义构建函数可以在所属组件的build方法和其他自定义构建函数中调用,但不允许在组件外调用。
- 在自定义函数体中,this指代当前所属组件,组件的状态变量可以在自定义构建函数内访问。建议通过this访问自定义组件的状态变量而不是参数传递。
限制条件
- @LocalBuilder只能在所属组件内声明,不允许全局声明。
- @LocalBuilder不能被内置装饰器和自定义装饰器使用。
- 自定义组件内的静态方法不能和@LocalBuilder一起使用。
@LocalBuilder和局部@Builder使用区别
@Builder方法引用传参时,为了改变this指向,使用bind(this)后,会导致组件的父子关系和状态管理的父子关系不一致,但是@LocalBuilder是否使用bind(this),都不会改变组件的父子关系。
看个例子:
@Component
struct Child {
label: string = `Child`;
@BuilderParam customBuilderParam: () => void;
build() {
Column() {
this.customBuilderParam()
}
}}
@Entry
@Component
struct Parent {
label: string = `Parent`;
@Builder componentBuilder() {
Text(`${this.label}`)
}
// @LocalBuilder componentBuilder() {
// Text(`${this.label}`)
// }
build() {
Column() {
Child({ customBuilderParam: this.componentBuilder })
}
}}
可以看到有两个个自定义组件Child和Parent组件内都有一个label属性,当我们使用@Builder装饰器修饰时,界面上会展示Child的label的内容,当使用@LocalBuilder装饰器修饰时,界面上会展示Parent的label的内容。
即:
- @Builder componentBuilder()通过this.componentBuilder的形式传给子组件@BuilderParam customBuilderParam,this指向在Child的label,即“Child”。
- @LocalBuilder componentBuilder()通过this.componentBuilder的形式传给子组件@BuilderParam customBuilderParam,this指向Parent的label,即“Parent”。
参数传递规则
与局部@Builder一致,可以参照@Builder
@BuilderParam
当开发者创建了自定义组件,并想对该组件添加特定功能时,如在自定义组件中添加一个点击跳转操作。若直接在组件内嵌入事件方法,将会导致所有引入该自定义组件的地方均增加了该功能。为解决此问题,ArkUI引入了@BuilderParam装饰器。@BuilderParam是用来承接@Builder函数的。开发者可以在初始化自定义组件时,使用不同的方式对@BuilderParam装饰的自定义构建函数进行传参赋值,在自定义组件内部通过调用@BuilderParam为组件增加特定的功能。该装饰器用于声明任意UI描述的一个元素,类似slot占位符。
使用说明
@BuilderParam装饰的方法只能被自定义构建函数(@Builder装饰的方法)初始化。
以下是三种使用方法,瞄一眼就知道怎么用了
// 在
@Builder function overBuilder() {}
@Component
struct Child {
@Builder doNothingBuilder() {};
// 使用自定义组件的自定义构建函数初始化@BuilderParam
@BuilderParam customBuilderParam: () => void = this.doNothingBuilder;
// 使用全局自定义构建函数初始化@BuilderParam
@BuilderParam customOverBuilderParam: () => void = overBuilder;
//使用父组件@Builder装饰的方法初始化子组件@BuilderParam
@BuilderParam customParentBuilderParam: () => void;
build(){
Column() {
this.customBuilderParam()
this.customOverBuilderParam()
this.customParentBuilderParam()
}
}
}
@Entry
@Component
struct Parent {
@Builder componentBuilder() {
Text(`Parent builder `)
}
build() {
Column() {
Child({ customParentBuilderParam: this.componentBuilder })
}
}
}
但是需要注意this的指向
@Component
struct Child {
label: string = `Child`;
@BuilderParam customBuilderParam: () => void;
@BuilderParam customChangeThisBuilderParam: () => void;
build() {
Column() {
this.customBuilderParam()
this.customChangeThisBuilderParam()
}
}
}
@Entry
@Component
struct Parent {
label: string = `Parent`;
@Builder componentBuilder() {
Text(`${this.label}`)
}
build() {
Column() {
//this指向当前@Entry所装饰的Parent组件,即label变量的值为"Parent"。
this.componentBuilder()
Child({
//this指向的是子组件Child,即label变量的值为"Child"。
customBuilderParam: this.componentBuilder,
//因为箭头函数的this指向的是宿主对象,所以label变量的值为"Parent"。
customChangeThisBuilderParam: (): void => { this.componentBuilder() }
})
}
}
}
我的理解:上面Parent的this.componentBuilder() 的this指向Parent毫无疑问。customBuilderParam: this.componentBuilder,就相当于将Parent中的@Builder放到了子组件中,那么this指向Child。箭头函数哪个就相当于将一个函数看作一个变量,当调用时函数本体在Parent 中,那么函数本体中的this当然还是Parent了。
实例演示
参数初始化组件
class Tmp{
label: string = '';
}
@Builder function overBuilder($$: Tmp) {
Text($$.label)
}
@Component
struct Child {
label: string = 'Child';
@Builder customBuilder() {};
// 无参数类型,指向的componentBuilder也是无参数类型
@BuilderParam customBuilderParam: () => void = this.customBuilder;
// 有参数类型,指向的overBuilder也是有参数类型的方法
@BuilderParam customOverBuilderParam: ($$: Tmp) => void = overBuilder;
build() {
Column() {
this.customBuilderParam()
this.customOverBuilderParam({label: 'global Builder label' } )
}
}
}
@Entry
@Component
struct Parent {
label: string = 'Parent';
@Builder componentBuilder() {
Text(`${this.label}`)
}
build() {
Column() {
this.componentBuilder()
Child({ customBuilderParam: this.componentBuilder, customOverBuilderParam: overBuilder })
}
}
}
官网示例,没什么难的,瞄一眼就知道了。
尾随闭包初始化组件
@Component
struct CustomContainer {
@Prop header: string = '';
@Builder closerBuilder(){};
// 使用父组件的尾随闭包{}(@Builder装饰的方法)初始化子组件@BuilderParam
@BuilderParam closer: () => void = this.closerBuilder;
build() {
Column() {
Text(this.header)
this.closer()
}
}
}
@Builder function specificParam(label1: string, label2: string) {
Column() {
Text(label1)
Text(label2)
}
}
@Entry
@Component
struct CustomContainerUser {
@State text: string = 'header';
build() {
Column() {
//在创建CustomContainer时跟一个大括号“{}”,形成尾随闭包
//作为传递给子组件CustomContainer @BuilderParam closer: () => void的参数
CustomContainer({ header: this.text }) {
Column() {
specificParam('testA', 'testB')
}.backgroundColor(Color.Yellow)
.onClick(() => {
this.text = 'changeHeader';
})
}
}
}
}
官网还有其他例子,不一一列出了,如果有过开发基础可以看出其实都是差不多的。瞄一眼就知道了
wrapBuilder
当在一个struct内使用了多个全局@Builder函数,来实现UI的不同效果时,多个全局@Builder函数会使代码维护起来非常困难,并且页面不整洁。此时,开发者可以使用wrapBuilder来封装全局@Builder。
使用说明
当@Builder方法赋值给变量或者数组后,赋值的变量或者数组在UI方法中无法使用。
@Builder
function builderElement() {}
let builderArr: Function[] = [builderElement];
@Builder
function testBuilder() {
ForEach(builderArr, (item: Function) => {
item();
})
}
在上述代码中,builderArr是一个@Builder方法组成的数组, 在ForEach中取每一项@Builder方法时会出现@Builder方法在UI方法中无法使用的错误。为了解决这一问题,引入wrapBuilder作为全局@Builder封装函数。wrapBuilder的参数返回WrappedBuilder对象,实现全局@Builder可以进行赋值和传递。
wrapBuilder是一个模板函数,返回一个WrappedBuilder对象。
declare function wrapBuilder< Args extends Object[]>(builder: (...args: Args) => void): WrappedBuilder;
同时 WrappedBuilder对象也是一个模板类。
declare class WrappedBuilder< Args extends Object[]> {
builder: (...args: Args) => void;
constructor(builder: (...args: Args) => void);
}
模板参数Args extends Object[]是需要包装的builder函数的参数列表
使用方法
let builderVar: WrappedBuilder<[string, number]> = wrapBuilder(MyBuilder)
let builderArr: WrappedBuilder<[string, number]>[] = [wrapBuilder(MyBuilder)] //可以放入数组
限制条件
- wrapBuilder方法只能传入
全局@Builder方法。 - wrapBuilder方法返回的WrappedBuilder对象的builder属性方法只能在struct内部使用。
实例演示
把@Builder装饰器装饰的方法MyBuilder作为wrapBuilder的参数,再将wrapBuilder赋值给变量globalBuilder,用来解决@Builder方法赋值给变量后无法被使用的问题。
@Builder
function MyBuilder(value: string, size: number) {
Text(value)
.fontSize(size)
}
let globalBuilder: WrappedBuilder<[string, number]> = wrapBuilder(MyBuilder);
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
Row() {
Column() {
globalBuilder.builder(this.message, 50)
}
.width('100%')
}
.height('100%')
}
}
ForEach的@Builder函数的渲染
@Builder
function MyBuilder(value: string, size: number) {
Text(value)
}
@Builder
function YourBuilder(value: string, size: number) {
Text(value)
}
//将 @Builder 的方法装入 builderArr
const builderArr: WrappedBuilder<[string, number]>[] = [wrapBuilder(MyBuilder), wrapBuilder(YourBuilder)];
@Entry
@Component
struct Index {
@Builder testBuilder() {
ForEach(builderArr, (item: WrappedBuilder<[string, number]>) => {
item.builder('Hello World', 30)
})
}
build() {
Row() {
Column() {
this.testBuilder()
}
.width('100%')
}
.height('100%')
}
}
重复定义wrapBuilder失效
通过wrapBuilder(MyBuilderFirst)初始化定义builderObj之后,再次对builderObj进行赋值wrapBuilder(MyBuilderSecond)会不起作用,只生效第一次定义的wrapBuilder(MyBuilderFirst)。
@Builder
function MyBuilderFirst(value: string, size: number) {
Text('MyBuilderFirst:' + value)
.fontSize(size)
}
@Builder
function MyBuilderSecond(value: string, size: number) {
Text('MyBuilderSecond:' + value)
.fontSize(size)
}
interface BuilderModel {
globalBuilder: WrappedBuilder<[string, number]>;
}
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
@State builderObj: BuilderModel = { globalBuilder: wrapBuilder(MyBuilderFirst) };
aboutToAppear(): void {
setTimeout(() => {
//这里再去设置 是无效的
this.builderObj.globalBuilder = wrapBuilder(MyBuilderSecond);
},1000)
}
build() {
Row() {
Column() {
this.builderObj.globalBuilder.builder(this.message, 20)
}
.width('100%')
}
.height('100%')
}
}
@Styles
用于提炼公共样式进行复用的装饰器,@Styles装饰器可以将多条样式设置提炼成一个方法,直接在组件声明的位置调用。通过@Styles装饰器可以快速定义并复用自定义样式。
使用说明
- 当前@Styles仅支持通用属性和通用事件。
- @Styles方法不支持参数
// 反例: @Styles不支持参数 @Styles function globalFancy (value: number) { .width(value) } - @Styles可以定义在组件内或全局,在全局定义时需在方法名前面添加function关键字,组件内定义时则不需要添加function关键字。
// 全局 @Styles function functionName() { ... } // 在组件内 @Component struct FancyUse { @Styles fancy() { .height(100) } }
[! tip] 说明
只能在当前文件内使用,不支持export。
如果想实现export功能,推荐使用AttributeModifier
如果要实现跨文件操作的功能,可以参考使用动态属性设置
// index.ets
import { MyButtonModifier } from './setAttribute'
@Entry
@Component
struct attributeDemo {
@State modifier: MyButtonModifier = new MyButtonModifier()
build() {
Row() {
Column() {
Button("Button")
.attributeModifier(this.modifier)
.onClick(() => {
this.modifier.isDark = !this.modifier.isDark
})
}
.width('100%')
}
.height('100%')
}
}
// setAttribute.ets
export class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
isDark: boolean = false
applyNormalAttribute(instance: ButtonAttribute): void {
if (this.isDark) {
instance.backgroundColor(Color.Black)
} else {
instance.backgroundColor(Color.Red)
}
}
}
- 定义在组件内的@Styles可以通过this访问组件的常量和状态变量,并可以在@Styles里通过事件来改变状态变量的值,示例如下:
@Component struct FancyUse { @State heightValue: number = 100 @Styles fancy() { .height(this.heightValue) .backgroundColor(Color.Yellow) .onClick(() => { this.heightValue = 200 }) } } - 组件内@Styles的优先级高于全局@Styles。框架优先找当前组件内的@Styles,如果找不到,则会全局查找。
实例演示
// 定义在全局的@Styles封装的样式
@Styles function globalFancy () {
.width(150)
.height(100)
.backgroundColor(Color.Pink)
}
@Entry
@Component
struct FancyUse {
@State heightValue: number = 100
// 定义在组件内的@Styles封装的样式
@Styles fancy() {
.width(200)
.height(this.heightValue)
.backgroundColor(Color.Yellow)
.onClick(() => {
this.heightValue = 200
})
}
build() {
Column({ space: 10 }) {
// 使用全局的@Styles封装的样式
Text('FancyA')
.globalFancy()
.fontSize(30)
// 使用组件内的@Styles封装的样式
Text('FancyB')
.fancy()
.fontSize(30)
}
}
}
@Extend
@Extend用于扩展原生组件样式.
使用说明
@Extend(UIComponentName) function functionName { ... }
和@Styles不同,@Extend仅支持在全局定义,不支持在组件内部定义。
[!tip] 说明
只能在当前文件内使用,不支持export
如果想实现export功能,推荐使用AttributeModifier和@Styles不同,@Extend支持封装指定组件的私有属性、私有事件和自身定义的全局方法。
// @Extend(Text)可以支持Text的私有属性fontColor @Extend(Text) function fancy () { .fontColor(Color.Red) } // superFancyText可以调用预定义的fancy @Extend(Text) function superFancyText(size:number) { .fontSize(size) .fancy() }和@Styles不同,@Extend装饰的方法支持参数,开发者可以在调用时传递参数,调用遵循TS方法传值调用。
// xxx.ets @Extend(Text) function fancy (fontSize: number) { .fontColor(Color.Red) .fontSize(fontSize) } @Entry @Component struct FancyUse { build() { Row({ space: 10 }) { Text('Fancy') .fancy(16) Text('Fancy') .fancy(24) } } }@Extend装饰的方法的参数可以为function,作为Event事件的句柄。
@Extend(Text) function makeMeClick(onClick: () => void) { .backgroundColor(Color.Blue) .onClick(onClick) } @Entry @Component struct FancyUse { @State label: string = 'Hello World'; onClickHandler() { this.label = 'Hello ArkUI'; } build() { Row({ space: 10 }) { Text(`${this.label}`) .makeMeClick(() => {this.onClickHandler()}) } } }@Extend的参数可以为状态变量,当状态变量改变时,UI可以正常的被刷新渲染。
@Extend(Text) function fancy (fontSize: number) { .fontColor(Color.Red) .fontSize(fontSize) } @Entry @Component struct FancyUse { @State fontSizeValue: number = 20 build() { Row({ space: 10 }) { Text('Fancy') .fancy(this.fontSizeValue) .onClick(() => { this.fontSizeValue = 30 }) } } }
stateStyles
@Styles仅仅应用于静态页面的样式复用,stateStyles可以依据组件的内部状态的不同,快速设置不同样式。
使用说明
stateStyles是属性方法,可以根据UI内部状态来设置样式,类似于css伪类,但语法不同。
- focused:获焦态。
- normal:正常态。
- pressed:按压态。
- disabled:不可用态。
- selected:选中态。
实例演示
Button1处于第一个组件,Button2处于第二个组件。按压时显示为pressed态指定的黑色。先是Button1获焦并显示为focus态指定的粉色。当Button2获焦的时候,Button2显示为focus态指定的粉色,Button1失焦显示normal态指定的蓝色。
@Entry
@Component
struct StateStylesSample {
build() {
Column() {
Button('Button1')
.stateStyles({
focused: {
.backgroundColor('#ffffeef0')
},
pressed: {
.backgroundColor('#ff707070')
},
normal: {
.backgroundColor('#ff2787d9')
}
})
.margin(20)
Button('Button2')
.stateStyles({
focused: {
.backgroundColor('#ffffeef0')
},
pressed: {
.backgroundColor('#ff707070')
},
normal: {
.backgroundColor('#ff2787d9')
}
})
}.margin('30%')
}
}
Styles和stateStyles联合使用
@Entry
@Component
struct MyComponent {
@Styles normalStyle() {
.backgroundColor(Color.Gray)
}
@Styles pressedStyle() {
.backgroundColor(Color.Red)
}
build() {
Column() {
Text('Text1')
.fontSize(50)
.fontColor(Color.White)
.stateStyles({
normal: this.normalStyle,
pressed: this.pressedStyle,
})
}
}
}
@AnimatableExtend
@AnimatableExtend装饰器用于自定义可动画的属性方法,在这个属性方法中修改组件不可动画的属性。在动画执行过程时,通过逐帧回调函数修改不可动画属性值,让不可动画属性也能实现动画效果。也可通过逐帧回调函数每帧修改可动画属性的值,实现逐帧布局的效果。
- 可动画属性:如果一个属性方法在animation属性前调用,改变这个属性的值可以生效animation属性的动画效果,这个属性称为可动画属性。比如height、width、backgroundColor、translate属性,Text组件的fontSize属性等。
- 不可动画属性:如果一个属性方法在animation属性前调用,改变这个属性的值不能生效animation属性的动画效果,这个属性称为不可动画属性。比如Polyline组件的points属性等。
使用说明
@AnimatableExtend(UIComponentName) function functionName(value: typeName) {
.propertyName(value)
}
- @AnimatableExtend仅支持定义在全局,不支持在组件内部定义。
- @AnimatableExtend定义的函数参数类型必须为number类型或者实现 AnimatableArithmetic
接口的自定义类型。 - @AnimatableExtend定义的函数体内只能调用@AnimatableExtend括号内组件的属性方法。
AnimatableArithmetic接口说明

实例演示
通过改变Text组件宽度实现逐帧布局的效果。
@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)
}
}
@Require
@Require是校验@Prop、@State、@Provide、@BuilderParam和普通变量(无状态装饰器修饰的变量)是否需要构造传参的一个装饰器。
使用说明
当@Require装饰器和@Prop、@State、@Provide、@BuilderParam、普通变量(无状态装饰器修饰的变量)结合使用时,在构造该自定义组件时,@Prop、@State、@Provide、@BuilderParam和普通变量(无状态装饰器修饰的变量)必须在构造时传参。
限制条件
@Require装饰器仅用于装饰struct内的@Prop、@State、@Provide、@BuilderParam和普通变量(无状态装饰器修饰的变量)。
实例演示
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
@Builder buildTest() {
Row() {
Text('Hello, world')
.fontSize(30)
}
}
build() {
Row() {
Child({ regular_value: this.message, state_value: this.message, provide_value: this.message, initMessage: this.message, message: this.message,
buildTest: this.buildTest, initBuildTest: this.buildTest })
}
}
}
@Component
struct Child {
@Builder buildFunction() {
Column() {
Text('initBuilderParam')
.fontSize(30)
}
}
@Require regular_value: string = 'Hello';
@Require @State state_value: string = "Hello";
@Require @Provide provide_value: string = "Hello";
@Require @BuilderParam buildTest: () => void;
@Require @BuilderParam initBuildTest: () => void = this.buildFunction;
@Require @Prop initMessage: string = 'Hello';
@Require @Prop message: string;
build() {
Column() {
Text(this.initMessage)
.fontSize(30)
Text(this.message)
.fontSize(30)
this.initBuildTest();
this.buildTest();
}
.width('100%')
.height('100%')
}
}