HarmonyOS NEXT实战开发:自定义扩展(AttributeModifier)

往期鸿蒙全套实战精彩文章必看内容:


概述

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

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

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

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

可以看出,AttributeModifier的能力和灵活性更好,当前持续在构建全量的属性、事件设置能力。未来,AttributeModifier可以替代@Styles和@Extend几乎所有的能力,建议上述场景都使用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表示多态的场景,支持默认态、按压态、焦点态、禁用态、选择态。其中,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方法完成。
  • 一个组件上同时使用属性方法和applyNormalAttribute设置相同的属性,遵循属性覆盖原则,即后设置的属性生效。
  • 一个Modifier实例对象可以在多个组件上使用。
  • 一个组件上多次使用applyNormalAttribute设置不同的Modifier实例,每次状态变量刷新均会按顺序执行这些实例的方法属性设置,同样遵循属性覆盖原则。

属性设置与修改

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

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

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

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

@Entry
@Component
struct attributeDemo {
  // 支持用状态装饰器修饰,行为和普通的对象一致
  @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%')
  }
}

一个组件上同时使用属性方法和applyNormalAttribute设置相同的属性,遵循属性覆盖原则,即后设置的属性生效。

// button_modifier.ets
export class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
  isDark: boolean = false

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

  applyNormalAttribute(instance: ButtonAttribute): void {
    if (this.isDark) {
      instance.backgroundColor(Color.Black)
    } else {
      instance.backgroundColor(Color.Red)
        .borderColor(Color.Black)
        .borderWidth(2)
    }
  }
}
// demo.ets
import { MyButtonModifier } from './button_modifier';

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

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

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

// button_modifier.ets
export class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
  isDark: boolean = false

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

  applyNormalAttribute(instance: ButtonAttribute): void {
    if (this.isDark) {
      instance.backgroundColor(Color.Black)
        .width(200)
    } else {
      instance.backgroundColor(Color.Red)
        .width(100)
    }
  }
}
// button_modifier2.ets
export class MyButtonModifier2 implements AttributeModifier<ButtonAttribute> {
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')
}
}
}
// demo.ets
import { MyButtonModifier } from './button_modifier';
import { MyButtonModifier2 } from './button_modifier2';

@Entry
@Component
struct attributeDemo {
  @State modifier: MyButtonModifier = new MyButtonModifier(true);
  @State modifier2: MyButtonModifier2 = new MyButtonModifier2(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设置多态样式、事件,实现事件逻辑的复用,支持默认态、按压态、焦点态、禁用态、选择态。例如如果需要在组件进入按压态时设置某些属性,就可以通过自定义实现applyPressedAttribute方法完成。

// button_modifier.ets
export class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
  applyNormalAttribute(instance: ButtonAttribute): void {
    // instance为Button的属性对象,设置正常状态下属性值
    instance.backgroundColor(Color.Red)
      .borderColor(Color.Black)
      .borderWidth(2)
  }

  applyPressedAttribute(instance: ButtonAttribute): void {
    // instance为Button的属性对象,设置按压状态下属性值
    instance.backgroundColor(Color.Green)
      .borderColor(Color.Orange)
      .borderWidth(5)
  }
}
// demo.ets
import { MyButtonModifier } from './button_modifier'

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

  build() {
    Row() {
      Column() {
        Button("Button")
          .attributeModifier(this.modifier)
      }
      .width('100%')
    }
    .height('100%')
  }
}

看完三件事❤️

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注作者 ,不定期分享原创知识。
  • 同时可以期待后续文章ing🚀。   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值