本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新

在ArkUI开发中,经常需要复用样式代码。HarmonyOS提供了@Styles@Extend装饰器来解决样式复用问题,但它们存在一些局限性:

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

核心痛点:

  • @Styles和@Extend都是编译期处理,无法跨文件导出复用

  • @Styles只能支持通用属性/事件,不支持组件特有属性

  • @Styles虽支持多态样式,但不支持传参

  • 无法编写业务逻辑动态决定属性设置,只能通过三元表达式全量设置

AttributeModifier的优势:

  • 支持跨文件导出复用

  • 支持参数传递

  • 支持业务逻辑编写

  • 支持多态样式(按压态、焦点态等)

  • 可通过状态变量触发UI刷新

二、接口定义

2.1 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;    // 选择态
}

接口说明:

  • T:组件的属性类型(如ButtonAttribute、CommonAttribute等)

  • 所有方法均为可选实现,根据需要选择实现对应场景

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

    方法回调时会传入组件属性对象instance,通过该对象设置属性

2.2 组件通用方法

使用方式:

  • 组件实例化时调用attributeModifier方法

  • 传入自定义的Modifier实例

  • T必须指定为组件对应的Attribute类型或CommonAttribute

三、使用示例

3.1 基础使用:设置和修改组件属性

实现自定义Modifier
// Common/ButtonModifier01.ets
export class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
    // 定义私有成员变量,支持外部动态修改
    public isDark: boolean = false

    // 构造函数支持传参
    constructor(dark?: boolean) {
        this.isDark = dark ?? false
    }

    applyNormalAttribute(instance: ButtonAttribute): void {
        // 支持业务逻辑编写
        if (this.isDark) {
            // 注意:变化前已设置且变化后未设置的属性会恢复为默认值
            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%')
    }
}

运行效果:

  • 初始状态:深色模式(isDark=true),按钮颜色为#707070

  • 点击按钮:切换isDark值,按钮颜色在#17A98D和#707070之间切换

3.2 属性覆盖原则

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

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

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

运行逻辑:

  1. 初始时,Button先设置backgroundColor为蓝色

  2. 随后attributeModifier设置modifier(深色模式为#707070)

  3. 由于modifier后设置,最终显示#707070

  4. 点击切换时,modifier的isDark变化,触发UI刷新重新应用属性

3.3 多个Modifier组合使用

一个组件上可以多次使用attributeModifier设置不同的Modifier实例,按顺序执行,同样遵循后设置的属性生效原则。

// Common/ButtonModifier02.ets
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)
        }
    }
}

// Common/ButtonModifier03.ets
export class MyButtonModifier3 implements AttributeModifier<ButtonAttribute> {
    public isDark2: boolean = false

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

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

// pages/Button3.ets
@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)  // 后执行,覆盖前面的backgroundColor设置
                    .onClick(() => {
                        this.modifier.isDark = !this.modifier.isDark
                        this.modifier2.isDark2 = !this.modifier2.isDark2
                    })
            }
            .width('100%')
        }
        .height('100%')
    }
}

执行顺序:

  1. 先执行modifier,设置backgroundColor和width

  2. 再执行modifier2,重新设置backgroundColor(覆盖modifier的设置)

  3. width仍保留modifier的设置(modifier2未设置width)

  4. 最终效果:backgroundColor由modifier2决定,width由modifier决定

3.4 多态样式与事件设置

AttributeModifier支持完整的多态样式,包括:

  • 默认态(Normal)

  • 按压态(Pressed)

  • 焦点态(Focused)

  • 禁用态(Disabled)

  • 选择态(Selected)

// Common/ButtonModifier04.ets
export class MyButtonModifier4 implements AttributeModifier<ButtonAttribute> {
    applyNormalAttribute(instance: ButtonAttribute): void {
        // 正常状态下的样式
        instance.backgroundColor('#17A98D')
            .borderColor('#707070')
            .borderWidth(2)
    }

    applyPressedAttribute(instance: ButtonAttribute): void {
        // 按压状态下的样式
        instance.backgroundColor('#2787D9')
            .borderColor('#FFC000')
            .borderWidth(5)
    }
}

// pages/Button4.ets
@Entry
@Component
struct Button4 {
    @State modifier: MyButtonModifier4 = new MyButtonModifier4();

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

交互效果:

  • 正常显示:绿色背景(#17A98D),灰色边框(#707070),边框宽度2

  • 按下时:蓝色背景(#2787D9),黄色边框(#FFC000),边框宽度5

  • 松开后:恢复正常样式

四、使用原则

4.1 触发时机

  • 组件首次初始化时触发applyNormalAttribute

  • 关联的状态变量发生变化时触发对应方法

  • 组件进入不同状态(按压、焦点等)时触发对应的applyXxxAttribute方法

4.2 属性恢复规则

属性变化触发applyXxxAttribute函数时,该组件之前已设置的属性,在本次变化后未设置的属性会恢复为属性的默认值

applyNormalAttribute(instance: ButtonAttribute): void {
    if (this.isDark) {
        // 只设置了backgroundColor,borderColor和borderWidth会恢复默认值
        instance.backgroundColor('#707070')
    } else {
        // 同时设置了三个属性
        instance.backgroundColor('#17A98D')
            .borderColor('#707070')
            .borderWidth(2)
    }
}

4.3 实例复用

一个Modifier实例对象可以在多个组件上使用:

@State modifier: MyButtonModifier = new MyButtonModifier(true);

build() {
    Column() {
        Button('按钮1').attributeModifier(this.modifier)
        Button('按钮2').attributeModifier(this.modifier)
        Button('按钮3').attributeModifier(this.modifier)
    }
}

4.4 异常处理

对于暂未支持的属性/事件,执行时会抛异常。

五、属性/事件支持情况详表

5.1 不支持attributeModifier的属性/事件

以下属性/事件当前不支持通过attributeModifier设置:

组件/通用信息 属性/事件名称 说明
CommonAttribute accessibilityText -
CommonAttribute accessibilityDescription -
CommonAttribute animation 不支持animation相关属性
CommonAttribute attributeModifier 不支持嵌套使用,不生效
CommonAttribute backgroundFilter -
CommonAttribute chainWeight -

被注:完整列表请参考官方文档

5.2 起始版本与支持版本不一致的属性

部分属性的起始版本与支持attributeModifier的版本不一致:

组件 属性/事件 起始版本 支持Modifier版本
Button buttonStyle 11 12
Button controlSize 11 12
CommonAttribute onDragStart 8 13
CommonAttribute onVisibleAreaChange 9 20
CommonAttribute foregroundBlurStyle 10 18

备注:完整列表请参考官方文档

六、其他

6.1 什么时候使用AttributeModifier?

  • 需要跨文件复用样式代码

  • 需要根据业务逻辑动态设置属性

  • 需要支持参数传递的样式复用

  • 需要实现多态样式(按压态、焦点态等)

  • 需要将UI样式与业务逻辑分离

6.2 优化建议

  1. 合理拆分Modifier:将不同功能的属性拆分到不同的Modifier中,便于维护和复用

  2. 避免不必要的状态变量:只有需要动态变化的属性才使用状态变量

  3. 注意属性恢复规则:在apply方法中设置所有需要的属性,避免意外恢复默认值

6.3 设计模式推荐

// 推荐:使用Builder模式创建Modifier
export class ButtonModifierBuilder {
    private modifier: MyButtonModifier;
    
    constructor() {
        this.modifier = new MyButtonModifier();
    }
    
    setDarkMode(isDark: boolean): ButtonModifierBuilder {
        this.modifier.isDark = isDark;
        return this;
    }
    
    build(): MyButtonModifier {
        return this.modifier;
    }
}

// 使用
const modifier = new ButtonModifierBuilder()
    .setDarkMode(true)
    .build();

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐