本文介绍鸿蒙中的装饰器的作用

@struct

自定义组件基于struct实现,struct + 自定义组件名 + {…}的组合构成自定义组件,不能有继承关系。对于struct的实例化,可以省略new。

@Component

@Component装饰器仅能装饰struct关键字声明的数据结构。struct被@Component装饰后具备组件化的能力,需要实现build方法描述UI,一个struct只能被一个@Component装饰。@Component可以接受一个可选的bool类型参数。

@Component({ freezeWhenInactive: true })

是否开启组件冻结,

  1. 启用后,系统将仅对处于激活状态的自定义组件进行更新,这使得UI框架可以尽量缩小更新范围,仅限于用户可见范围内(激活状态)的自定义组件,从而提高复杂UI场景下的刷新效率。
  2. 当之前处于inactive状态的自定义组件重新变为active状态时,状态管理框架会对其执行必要的刷新操作,确保UI的正确展示。
    其实就是优化界面UI刷新的性能。就是只刷新当前可见的自定义组件,对于不可见的自定义组件的刷新会延迟到他们可见时。
    注意,组件active/inactive并不等同于肉眼可见,而是他们在不在屏幕内
  3. 页面路由:当前栈顶页面为active,非栈顶不可见页面为inactive。
  4. TabContent:只有当前显示的TabContent中的自定义组件处于active状态,其余则为inactive
  5. LazyForEach:仅当前显示的LazyForEach中的自定义组件为active,而缓存节点的组件则为inactive。
  6. 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} `)  
  }  
}

打开页面展示:68381749-8464-4a2a-94a4-55be6cc91ae8.jpeg
当点击后显示:ed3d7c26-9336-4f9d-adc5-7c36c598ad42.jpeg 可以看到文本已经改变了
当按引用传递参数时,如果在@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 })  
    }  
  }}

可以看到有两个个自定义组件ChildParent组件内都有一个label属性,当我们使用@Builder装饰器修饰时,界面上会展示Childlabel的内容,当使用@LocalBuilder装饰器修饰时,界面上会展示Parentlabel的内容。
即:

  • @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() }
      })
    }
  }
}

我的理解:上面Parentthis.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接口说明

image.png

实例演示

通过改变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%')
  }
}



©超超使用Stellar 1.29.1 创建。

发表了 14 篇文章 · 总计 21.5k 字