1# 属性修改器 (AttributeModifier) 2 3## 概述 4声明式语法引入了[@Styles](../quick-start/arkts-style.md)和[@Extend](../quick-start/arkts-extend.md)两个装饰器,可以解决复用相同自定义样式的问题,但是存在以下受限场景: 5- @Styles和@Extend均是编译期处理,不支持跨文件的导出复用。 6- @Styles仅能支持通用属性、事件,不支持组件特有的属性。 7- @Styles虽然支持在多态样式下使用,但不支持传参,无法对外开放一些属性。 8- @Extend虽然能支持特定组件的私有属性、事件,但同样不支持跨文件导出复用。 9- @Styles、@Extend对于属性设置,无法支持业务逻辑编写,动态决定是否设置某些属性,只能通过三元表达式对所有可能设置的属性进行全量设置,设置大量属性时效率较低。 10 11 12为了解决上述问题,ArkUI引入了AttributeModifier机制,可以通过Modifier对象动态修改属性。能力对比如下: 13| 能力 | @Styles | @Extend | AttributeModifier | 14| :-----: | :-----: | :-----: | :-----: | 15| 跨文件导出 | 不支持 | 不支持 | 支持 | 16| 通用属性设置 | 支持 | 支持 | 支持 | 17| 通用事件设置 | 支持 | 支持 | 部分支持 | 18| 组件特有属性设置 | 不支持 | 支持 | 部分支持 | 19| 组件特有事件设置 | 不支持 | 支持 | 部分支持 | 20| 参数传递 | 不支持 | 支持 | 支持 | 21| 多态样式 | 支持 | 不支持 | 支持 | 22| 业务逻辑 | 不支持 | 不支持 | 支持 | 23 24可以看出,与@Styles和@Extend相比,AttributeModifier提供了更强的能力和灵活性,且在持续完善全量的属性和事件设置能力,因此推荐优先使用AttributeModifier。 25 26## 接口定义 27 28```ts 29declare interface AttributeModifier<T> { 30 31 applyNormalAttribute?(instance: T): void; 32 33 applyPressedAttribute?(instance: T): void; 34 35 applyFocusedAttribute?(instance: T): void; 36 37 applyDisabledAttribute?(instance: T): void; 38 39 applySelectedAttribute?(instance: T): void; 40 41} 42``` 43 44`AttributeModifier`是一个接口,开发者需要实现其中的`applyXxxAttribute`方法来实现对应场景的属性设置。`Xxx`表示多态的场景,支持默认态(`Normal`)、按压态(`Pressed`)、焦点态(`Focused`)、禁用态(`Disabled`)、选择态(`Selected`)。`T`是组件的属性类型,开发者可以在回调中获取到属性对象,通过该对象设置属性。 45 46```ts 47declare class CommonMethod<T> { 48 attributeModifier(modifier: AttributeModifier<T>): T; 49} 50``` 51 52组件的通用方法增加了`attributeModifier`方法,支持传入自定义的Modifier。由于组件在实例化时会明确`T`的类型,所以调用该方法时,`T`必须指定为组件对应的Attribute类型,或者是`CommonAttribute`。 53 54## 使用说明 55 56- 组件通用方法`attributeModifier`支持传入一个实现`AttributeModifier<T>`接口的实例,`T`必须指定为组件对应的Attribute类型,或者是`CommonAttribute`。 57- 在组件首次初始化或者关联的状态变量发生变化时,如果传入的实例实现了对应接口,会触发`applyNormalAttribute`。 58- 回调`applyNormalAttribute`时,会传入组件属性对象,通过该对象可以设置当前组件的属性/事件。 59- 暂未支持的属性/事件,执行时会抛异常。 60- 属性变化触发`applyXxxAttribute`函数时,该组件之前已设置的属性,在本次变化后未设置的属性会恢复为属性的默认值。 61- 可以通过该接口使用多态样式的功能,例如如果需要在组件进入按压态时设置某些属性,就可以通过自定义实现`applyPressedAttribute`方法完成。 62- 一个组件上同时使用属性方法和`applyNormalAttribute`设置相同的属性,遵循属性覆盖原则,即后设置的属性生效。 63- 一个Modifier实例对象可以在多个组件上使用。 64- 一个组件上多次使用`applyNormalAttribute`设置不同的Modifier实例,每次状态变量刷新均会按顺序执行这些实例的方法属性设置,同样遵循属性覆盖原则。 65 66## 设置和修改组件属性 67 68AttributeModifier可以分离UI与样式,支持参数传递及业务逻辑编写,并且通过状态变量触发刷新。 69 70 ```ts 71 // button_modifier.ets 72 export class MyButtonModifier implements AttributeModifier<ButtonAttribute> { 73 // 可以实现一个Modifier,定义私有的成员变量,外部可动态修改 74 isDark: boolean = false 75 76 // 通过构造函数,创建时传参 77 constructor(dark?: boolean) { 78 this.isDark = dark ? dark : false 79 } 80 81 applyNormalAttribute(instance: ButtonAttribute): void { 82 // instance为Button的属性对象,可以通过instance对象对属性进行修改 83 if (this.isDark) { // 支持业务逻辑的编写 84 // 属性变化触发apply函数时,变化前已设置并且变化后未设置的属性会恢复为默认值 85 instance.backgroundColor('#707070') 86 } else { 87 // 支持属性的链式调用 88 instance.backgroundColor('#17A98D') 89 .borderColor('#707070') 90 .borderWidth(2) 91 } 92 } 93 } 94 ``` 95 ```ts 96 // demo.ets 97 import { MyButtonModifier } from './button_modifier' 98 99 @Entry 100 @Component 101 struct attributeDemo { 102 // 支持用状态装饰器修饰,行为和普通的对象一致 103 @State modifier: MyButtonModifier = new MyButtonModifier(true); 104 105 build() { 106 Row() { 107 Column() { 108 Button("Button") 109 .attributeModifier(this.modifier) 110 .onClick(() => { 111 // 对象的一层属性被修改时,会触发UI刷新,重新执行applyNormalAttribute 112 this.modifier.isDark = !this.modifier.isDark 113 }) 114 } 115 .width('100%') 116 } 117 .height('100%') 118 } 119 } 120 ``` 121  122 123当一个组件上同时使用属性方法和`applyNormalAttribute`设置相同的属性时,遵循属性覆盖原则,即后设置的属性生效。 124 125 ```ts 126 // button_modifier.ets 127 export class MyButtonModifier implements AttributeModifier<ButtonAttribute> { 128 isDark: boolean = false 129 130 constructor(dark?: boolean) { 131 this.isDark = dark ? dark : false 132 } 133 134 applyNormalAttribute(instance: ButtonAttribute): void { 135 if (this.isDark) { 136 instance.backgroundColor('#707070') 137 } else { 138 instance.backgroundColor('#17A98D') 139 .borderColor('#707070') 140 .borderWidth(2) 141 } 142 } 143 } 144 ``` 145 ```ts 146 // demo.ets 147 import { MyButtonModifier } from './button_modifier'; 148 149 @Entry 150 @Component 151 struct attributeDemo { 152 @State modifier: MyButtonModifier = new MyButtonModifier(true); 153 154 build() { 155 Row() { 156 Column() { 157 // 先设置属性,后设置modifier,按钮颜色会跟随modifier的值改变 158 Button("Button") 159 .backgroundColor('#2787D9') 160 .attributeModifier(this.modifier) 161 .onClick(() => { 162 this.modifier.isDark = !this.modifier.isDark 163 }) 164 } 165 .width('100%') 166 } 167 .height('100%') 168 } 169 } 170 ``` 171  172 173当一个组件上多次使用`applyNormalAttribute`设置不同的Modifier实例时,每次状态变量刷新均会按顺序执行这些实例的方法属性设置,遵循属性覆盖原则,即后设置的属性生效。 174 175 ```ts 176 // button_modifier.ets 177 export class MyButtonModifier implements AttributeModifier<ButtonAttribute> { 178 isDark: boolean = false 179 180 constructor(dark?: boolean) { 181 this.isDark = dark ? dark : false 182 } 183 184 applyNormalAttribute(instance: ButtonAttribute): void { 185 if (this.isDark) { 186 instance.backgroundColor(Color.Black) 187 .width(200) 188 } else { 189 instance.backgroundColor(Color.Red) 190 .width(100) 191 } 192 } 193 } 194 ``` 195 ```ts 196 // button_modifier2.ets 197 export class MyButtonModifier2 implements AttributeModifier<ButtonAttribute> { 198 isDark2: boolean = false 199 200 constructor(dark?: boolean) { 201 this.isDark2 = dark ? dark : false 202 } 203 204 applyNormalAttribute(instance: ButtonAttribute): void { 205 if (this.isDark2) { 206 instance.backgroundColor('#2787D9') 207 } else { 208 instance.backgroundColor('#707070') 209 } 210 } 211 } 212 ``` 213 ```ts 214 // demo.ets 215 import { MyButtonModifier } from './button_modifier'; 216 import { MyButtonModifier2 } from './button_modifier2'; 217 218 @Entry 219 @Component 220 struct attributeDemo { 221 @State modifier: MyButtonModifier = new MyButtonModifier(true); 222 @State modifier2: MyButtonModifier2 = new MyButtonModifier2(true); 223 224 build() { 225 Row() { 226 Column() { 227 Button("Button") 228 .attributeModifier(this.modifier) 229 .attributeModifier(this.modifier2) 230 .onClick(() => { 231 this.modifier.isDark = !this.modifier.isDark 232 this.modifier2.isDark2 = !this.modifier2.isDark2 233 }) 234 } 235 .width('100%') 236 } 237 .height('100%') 238 } 239 } 240 ``` 241  242 243## 设置多态样式、事件 244 245使用`AttributeModifier`设置多态样式、事件,实现事件逻辑的复用,支持默认态(`Normal`)、按压态(`Pressed`)、焦点态(`Focused`)、禁用态(`Disabled`)、选择态(`Selected`)。例如如果需要在组件进入按压态时设置某些属性,就可以通过自定义实现`applyPressedAttribute`方法完成。 246 247 ```ts 248 // button_modifier.ets 249 export class MyButtonModifier implements AttributeModifier<ButtonAttribute> { 250 applyNormalAttribute(instance: ButtonAttribute): void { 251 // instance为Button的属性对象,设置正常状态下属性值 252 instance.backgroundColor('#17A98D') 253 .borderColor('#707070') 254 .borderWidth(2) 255 } 256 257 applyPressedAttribute(instance: ButtonAttribute): void { 258 // instance为Button的属性对象,设置按压状态下属性值 259 instance.backgroundColor('#2787D9') 260 .borderColor('#FFC000') 261 .borderWidth(5) 262 } 263 } 264 ``` 265 ```ts 266 // demo.ets 267 import { MyButtonModifier } from './button_modifier' 268 269 @Entry 270 @Component 271 struct attributeDemo { 272 @State modifier: MyButtonModifier = new MyButtonModifier(); 273 274 build() { 275 Row() { 276 Column() { 277 Button("Button") 278 .attributeModifier(this.modifier) 279 } 280 .width('100%') 281 } 282 .height('100%') 283 } 284 } 285 286 ``` 287 