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  ![AttributeModifier](figures/AttributeModifier01.gif)
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  ![AttributeModifier](figures/AttributeModifier03.gif)
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  ![AttributeModifier](figures/AttributeModifier04.gif)
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  ![AttributeModifier](figures/AttributeModifier02.gif)