属性修改器 (AttributeModifier)

概述

声明式语法引入了@Styles@Extend两个装饰器,可以解决复用相同自定义样式的问题,但是存在以下受限场景:

  • @Styles和@Extend均是编译期处理,不支持跨文件的导出复用。
  • @Styles仅能支持通用属性、事件,不支持组件特有的属性。
  • @Styles虽然支持在多态样式下使用,但不支持传参,无法对外开放一些属性。
  • @Extend虽然能支持特定组件的私有属性、事件,但同样不支持跨文件导出复用。
  • @Styles、@Extend对于属性设置,无法支持业务逻辑编写,动态决定是否设置某些属性,只能通过三元表达式对所有可能设置的属性进行全量设置,设置大量属性时效率较低。

为了解决上述问题,ArkUI引入了AttributeModifier机制,可以通过Modifier对象动态修改属性。能力对比如下:

能力 @Styles @Extend AttributeModifier
跨文件导出 不支持 不支持 支持
通用属性设置 支持 支持 支持
通用事件设置 支持 支持 部分支持
组件特有属性设置 不支持 支持 部分支持
组件特有事件设置 不支持 支持 部分支持
参数传递 不支持 支持 支持
多态样式 支持 不支持 支持
业务逻辑 不支持 不支持 支持

可以看出,与@Styles和@Extend相比,AttributeModifier提供了更强的能力和灵活性,且在持续完善全量的属性和事件设置能力,因此推荐优先使用AttributeModifier。

接口定义

declare interface AttributeModifier<T> {

  applyNormalAttribute?(instance: T): void;

  applyPressedAttribute?(instance: T): void;

  applyFocusedAttribute?(instance: T): void;

  applyDisabledAttribute?(instance: T): void;

  applySelectedAttribute?(instance: T): void;

}

AttributeModifier是一个接口,开发者需要实现其中的applyXxxAttribute方法来实现对应场景的属性设置。Xxx表示多态的场景,支持默认态(Normal)、按压态(Pressed)、焦点态(Focused)、禁用态(Disabled)、选择态(Selected)。T是组件的属性类型,开发者可以在回调中获取到属性对象,通过该对象设置属性。

declare class CommonMethod<T> {
  attributeModifier(modifier: AttributeModifier<T>): T;
}

组件的通用方法增加了attributeModifier方法,支持传入自定义的Modifier。由于组件在实例化时会明确T的类型,所以调用该方法时,T必须指定为组件对应的Attribute类型,或者是CommonAttribute

使用说明

  • 组件通用方法attributeModifier支持传入一个实现AttributeModifier<T>接口的实例,T必须指定为组件对应的Attribute类型,或者是通用属性(CommonAttribute)。
  • 在组件首次初始化或者关联的状态变量发生变化时,如果传入的实例实现了对应接口,会触发applyNormalAttribute
  • 回调applyNormalAttribute时,会传入组件属性对象,通过该对象可以设置当前组件的属性/事件。
  • 暂未支持的属性/事件,执行时会抛异常。
  • 属性变化触发applyXxxAttribute函数时,该组件之前已设置的属性,在本次变化后未设置的属性会恢复为属性的默认值。
  • 可以通过该接口使用多态样式的功能,例如如果需要在组件进入按压态时设置某些属性,就可以通过自定义实现applyPressedAttribute方法完成。
  • 在attributeModifier中设置的属性尽量不要与其他方法设置的属性相同,避免在页面刷新时attributeModifier不生效。
  • 一个Modifier实例对象可以在多个组件上使用。
  • 一个组件上多次使用applyNormalAttribute设置不同的Modifier实例,每次状态变量刷新均会按顺序执行这些实例的方法属性设置,同样遵循属性覆盖原则。

设置和修改组件属性

AttributeModifier可以分离UI与样式,支持参数传递及业务逻辑编写,并且通过状态变量触发刷新。

export class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
  // 可以实现一个Modifier,定义私有的成员变量,外部可动态修改
  public isDark: boolean = false

  // 通过构造函数,创建时传参
  constructor(dark?: boolean) {
    this.isDark = dark ?? false
  }

  applyNormalAttribute(instance: ButtonAttribute): void {
    // instance为Button的属性对象,可以通过instance对象对属性进行修改
    if (this.isDark) { // 支持业务逻辑的编写
      // 属性变化触发apply函数时,变化前已设置并且变化后未设置的属性会恢复为默认值
      instance.backgroundColor('#707070')
    } else {
      // 支持属性的链式调用
      instance.backgroundColor('#17A98D')
        .borderColor('#707070')
        .borderWidth(2)
    }
  }
}

// pages/Button1.ets
import { MyButtonModifier } from '../Common/ButtonModifier01'

@Entry
@Component
struct Button1 {
  // 支持用状态装饰器修饰,行为和普通的对象一致
  @State modifier: MyButtonModifier = new MyButtonModifier(true);

  build() {
    Row() {
      Column() {
        Button('Button')
          .attributeModifier(this.modifier)
          .onClick(() => {
            // 对象的一层属性被修改时,会触发UI刷新,重新执行applyNormalAttribute
            this.modifier.isDark = !this.modifier.isDark
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

AttributeModifier

export class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
  // 可以实现一个Modifier,定义私有的成员变量,外部可动态修改
  public isDark: boolean = false

  // 通过构造函数,创建时传参
  constructor(dark?: boolean) {
    this.isDark = dark ?? false
  }

  applyNormalAttribute(instance: ButtonAttribute): void {
    // instance为Button的属性对象,可以通过instance对象对属性进行修改
    if (this.isDark) { // 支持业务逻辑的编写
      // 属性变化触发apply函数时,变化前已设置并且变化后未设置的属性会恢复为默认值
      instance.backgroundColor('#707070')
    } else {
      // 支持属性的链式调用
      instance.backgroundColor('#17A98D')
        .borderColor('#707070')
        .borderWidth(2)
    }
  }
}

// pages/Button2.ets
import { MyButtonModifier } from '../Common/ButtonModifier01'

@Entry
@Component
struct Button2 {
  @State modifier: MyButtonModifier = new MyButtonModifier(true);

  build() {
    Row() {
      Column() {
        // 先设置属性,后设置modifier,按钮颜色会跟随modifier的值改变
        Button('Button')
          .backgroundColor('#2787D9')
          .attributeModifier(this.modifier)
          .onClick(() => {
            this.modifier.isDark = !this.modifier.isDark
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

AttributeModifier

当一个组件上多次使用applyNormalAttribute设置不同的Modifier实例时,每次状态变量刷新均会按顺序执行这些实例的方法属性设置,遵循属性覆盖原则,即后设置的属性生效。

export class MyButtonModifier2 implements AttributeModifier<ButtonAttribute> {
  public isDark: boolean = false

  constructor(dark?: boolean) {
    this.isDark = dark ?? false
  }

  applyNormalAttribute(instance: ButtonAttribute): void {
    if (this.isDark) {
      instance.backgroundColor(Color.Black)
        .width(200)
    } else {
      instance.backgroundColor(Color.Red)
        .width(100)
    }
  }
}
export class MyButtonModifier3 implements AttributeModifier<ButtonAttribute> {
  public isDark2: boolean = false

  constructor(dark?: boolean) {
    this.isDark2 = dark ? dark : false
  }

  applyNormalAttribute(instance: ButtonAttribute): void {
    if (this.isDark2) {
      instance.backgroundColor('#2787D9')
    } else {
      instance.backgroundColor('#707070')
    }
  }
}

// pages/Button3.ets
import { MyButtonModifier2 } from '../Common/ButtonModifier02';
import { MyButtonModifier3 } from '../Common/ButtonModifier03';

@Entry
@Component
struct Button3 {
  @State modifier: MyButtonModifier2 = new MyButtonModifier2(true);
  @State modifier2: MyButtonModifier3 = new MyButtonModifier3(true);

  build() {
    Row() {
      Column() {
        Button('Button')
          .attributeModifier(this.modifier)
          .attributeModifier(this.modifier2)
          .onClick(() => {
            this.modifier.isDark = !this.modifier.isDark
            this.modifier2.isDark2 = !this.modifier2.isDark2
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}
![AttributeModifier](figures/AttributeModifier04.gif)

设置多态样式、事件

使用AttributeModifier设置多态样式、事件,实现事件逻辑的复用,支持默认态(Normal)、按压态(Pressed)、焦点态(Focused)、禁用态(Disabled)、选择态(Selected)。例如如果需要在组件进入按压态时设置某些属性,就可以通过自定义实现applyPressedAttribute方法完成。

export class MyButtonModifier4 implements AttributeModifier<ButtonAttribute> {
  applyNormalAttribute(instance: ButtonAttribute): void {
    // instance为Button的属性对象,设置正常状态下属性值
    instance.backgroundColor('#17A98D')
      .borderColor('#707070')
      .borderWidth(2)
  }

  applyPressedAttribute(instance: ButtonAttribute): void {
    // instance为Button的属性对象,设置按压状态下属性值
    instance.backgroundColor('#2787D9')
      .borderColor('#FFC000')
      .borderWidth(5)
  }
}

// pages/Button4.ets
import { MyButtonModifier4 } from '../Common/ButtonModifier04'

@Entry
@Component
struct Button4 {
  @State modifier: MyButtonModifier4 = new MyButtonModifier4();

  build() {
    Row() {
      Column() {
        Button('Button')
          .attributeModifier(this.modifier)
      }
      .width('100%')
    }
    .height('100%')
  }
}
![AttributeModifier](figures/AttributeModifier02.gif)

属性或事件对attributeModifier的支持情况

通过attributeModifier动态设置属性或事件的能力从API version 11开始支持。

属性或事件不支持attributeModifier的范围

下表说明了当前不支持attributeModifier的属性或事件。若无特殊说明,属性或事件默认在首次开放时支持attributeModifier。

组件通用信息/系统组件的名称 属性/事件的名称 告警信息 说明
CommonAttribute accessibilityText - -
CommonAttribute accessibilityDescription - -
CommonAttribute animation Method not implemented. 不支持animation相关属性。
CommonAttribute attributeModifier - attributeModifier不支持嵌套使用,不生效。
CommonAttribute backgroundFilter is not callable -
CommonAttribute chainWeight is not callable -
CommonAttribute compositingFilter is not callable -
CommonAttribute drawModifier is not callable 不支持modifier相关的属性。
CommonAttribute foregroundFilter is not callable -
CommonAttribute freeze is not callable -
CommonAttribute gesture Method not implemented. 不支持gesture相关的属性。
CommonAttribute gestureModifier is not callable 不支持modifier相关的属性。
CommonAttribute onAccessibilityHover is not callable -
CommonAttribute onDigitalCrown is not callable. -
CommonAttribute parallelGesture Method not implemented. 不支持gesture相关的属性。
CommonAttribute priorityGesture Method not implemented. 不支持gesture相关的属性。
CommonAttribute reuseId Method not implemented. -
CommonAttribute stateStyles Method not implemented. 不支持stateStyles相关的属性。
CommonAttribute useSizeType Method not implemented. 不支持已废弃属性。
CommonAttribute visualEffect is not callable -
CommonAttribute bindContextMenu Method not implemented. 不支持入参为CustomBuilder。
CommonAttribute bindContentCover Method not implemented. 不支持入参为CustomBuilder。
CommonAttribute bindSheet Method not implemented. 不支持入参为CustomBuilder。
CommonAttribute dragPreview Builder is not supported. 不支持入参为CustomBuilder。
CommonAttribute bindPopup Method not implemented. 不支持入参为CustomBuilder。
CommonAttribute accessibilityVirtualNode is not callable 不支持入参为CustomBuilder。
CommonAttribute chainWeight - -
CheckboxGroup contentModifier - -
CommonAttribute backgroundImage - -
CommonAttribute onClick - -
CommonAttribute toolbar - -
CommonAttribute accessibilityGroup - -
CommonAttribute reuse - -
CommonAttribute onGestureRecognizerJudgeBegin - -
EmbeddedComponent onError - -
EmbeddedComponent onTerminated - -
NavDestination backButtonIcon19+ - -
NavDestination menus19+ - -
NavDestination customTransition - -
Navigation backButtonIcon - -
Navigation menus - -
Repeat each - -
Repeat key - -
Repeat virtualScroll - -
Repeat template - -
Repeat templateId - -
Search customKeyboard - -
Search onWillAttachIME - -
Select menuItemContentModifier12+ - -
Select menuItemContentModifier18+ - -
Select textModifier - -
Select arrowModifier - -
Select optionTextModifier - -
Select selectedOptionTextModifier - -
Slider digitalCrownSensitivity - -
Swiper prevMargin - -
Swiper nextMargin - -
TextArea customKeyboard - -
Text bindSelectionMenu - -
TextInput customKeyboard - -
TextInput onWillAttachIME - -
TextPicker onEnterSelectedArea - -
TimePicker onEnterSelectedArea - -

属性或事件的起始版本与支持attributeModifier版本不一致的范围

下表说明了属性或事件的起始版本与默认支持attributeModifier版本不一致的情况。若无特殊说明,属性或事件默认在首次开放时支持attributeModifier。

组件通用信息/系统组件的名称 属性/事件的名称 属性/事件的起始版本 支持attributeModifier的版本
AlphabetIndexer autoCollapse 11 12
Button buttonStyle 11 12
Button controlSize 11 12
CalendarPicker onChange 18 20
Canvas enableAnalyzer 12 20
CommonAttribute accessibilityTextHint 12 20
CommonAttribute accessibilityChecked 13 20
CommonAttribute accessibilitySelected 13 20
CommonAttribute background 10 20
CommonAttribute visualEffect 12 20
CommonAttribute onDragStart 8 13
CommonAttribute onVisibleAreaApproximateChange 17 23
CommonAttribute onVisibleAreaChange 9 20
CommonAttribute onTouchIntercept 12 20
CommonAttribute onPreDrag 12 20
CommonAttribute onChildTouchTest 11 20
CommonAttribute backgroundFilter 12 20
CommonAttribute foregroundFilter 12 20
CommonAttribute compositingFilter 12 20
CommonAttribute foregroundBlurStyle 10 18
CommonAttribute freeze12+ 12 20
CommonAttribute freeze18+ 18 20
CommonAttribute dragPreviewOptions 11 12
CommonAttribute bindMenu 11 20
CommonAttribute transition 12 20
CommonAttribute safeAreaPadding 14 18
CommonAttribute pixelRound 11 12
ContainerSpan textBackgroundStyle 11 12
DatePicker onDateChange 18 20
FolderStack alignContent 11 12
FolderStack onFolderStateChange 11 20
FolderStack onHoverStatusChange 11 20
FolderStack enableAnimation 11 12
FolderStack autoHalfFold 11 12
Gauge privacySensitive 12 20
Image enableAnalyzer 11 12
Image resizable 11 20
List OnScrollVisibleContentChangeCallback 12 14
List onItemDragStart 8 14
NavDestination title 9 12
NavDestination mode 11 12
NavDestination backButtonIcon11+ 11 12
NavDestination menus12+ 12 14
NavDestination toolbarConfiguration 13 20
NavDestination onReady 11 20
NavDestination onWillAppear 12 20
NavDestination onWillDisappear 12 20
NavDestination onWillShow 12 20
NavDestination onWillHide 12 20
NavDestination systemBarStyle 12 20
NavDestination onResult 15 22
NavDestination bindToScrollable 14 22
NavDestination bindToNestedScrollable 14 22
NavDestination onActive 17 22
NavDestination onInactive 17 22
NavDestination onNewParam 19 22
Navigation title 8 12
Navigation toolbarConfiguration 10 20
Navigation customNavContentTransition 11 20
Navigation systemBarStyle 12 20
Navigation enableVisibilityLifecycleWithContentCover 21 23
PatternLock backgroundColor 9 20
PatternLock onDotConnect 11 20
Progress privacySensitive 12 20
Refresh onOffsetChange 12 20
RichEditor customKeyboard 10 23
RichEditor onDidIMEInput 12 20
RichEditor enablePreviewText 12 18
RichEditor placeholder 12 18
RichEditor onWillChange 12 18
RichEditor onDidChange 12 18
RichEditor editMenuOptions 12 18
RichEditor enableKeyboardOnFocus 12 18
RichEditor enableHapticFeedback 13 20
RichEditor barState 13 18
Select menuBackgroundColor 11 12
Select menuBackgroundBlurStyle 11 12
Swiper displayCount 8 12
SymbolGlyph fontSize 11 12
SymbolGlyph fontColor 11 12
SymbolGlyph fontWeight 11 12
SymbolGlyph effectStrategy 11 12
SymbolGlyph renderingStrategy 11 12
SymbolSpan fontSize 11 12
SymbolSpan fontColor 11 12
SymbolSpan fontWeight 11 12
SymbolSpan effectStrategy 11 12
SymbolSpan renderingStrategy 11 12
ScrollableCommonAttribute onWillScroll 12 14
ScrollableCommonAttribute onDidScroll 12 14
TabContent onWillShow 12 20
TabContent onWillHide 12 20
Tabs edgeEffect 12 17
Tabs customContentTransition 11 20
Tabs onContentWillChange 12 20
Tabs barBackgroundBlurStyle 11 12
TextArea enterKeyType 11 12
Text enableHapticFeedback 13 18
TextInput showCounter 11 12
TextInput onSecurityStateChange 12 20
TextPicker onScrollStop14+ 14 20
TextPicker onScrollStop18+ 18 20
TextTimer textShadow 11 12
TimePicker enableHapticFeedback 12 18
TimePicker onChange 18 20
Video enableAnalyzer 12 20
Video analyzerConfig 12 20
Video onError 7 20
WaterFlow onScrollIndex 11 20