• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 动态构建UI元素
2
3[基本UI描述](arkts-basic-ui-description.md)介绍的是如何创建一个内部UI结构固定的自定义组件,为了满足开发者自定义组件内部UI结构的需求,ArkTS同时提供了动态构建UI元素的能力。
4
5## @Builder
6
7可通过@Builder装饰器进行描述,该装饰器可以修饰一个函数,此函数可以在build函数之外声明,并在build函数中或其他@Builder修饰的函数中使用,从而实现在一个自定义组件内快速生成多个布局内容。使用方式如下面示例所示。
8
9```ts
10// xxx.ets
11@Component
12struct CompB {
13  @State CompValue: string = ''
14
15  aboutToAppear() {
16    console.info('CompB aboutToAppear.')
17  }
18
19  aboutToDisappear() {
20    console.info('CompB aboutToDisappear.')
21  }
22
23  build() {
24    Column() {
25      Button(this.CompValue)
26        .margin(5)
27    }
28  }
29}
30
31@Entry
32@Component
33struct CompA {
34  size1: number = 100
35  @State CompValue1: string = "Hello,CompValue1"
36  @State CompValue2: string = "Hello,CompValue2"
37  @State CompValue3: string = "Hello,CompValue3"
38
39  // @Builder装饰的函数CompC内使用自定义组件CompB
40  @Builder CompC(value: string) {
41    CompB({ CompValue: value })
42  }
43
44  @Builder SquareText(label: string) {
45    Text(label)
46      .fontSize(18)
47      .width(1 * this.size1)
48      .height(1 * this.size1)
49  }
50
51  // @Builder装饰的函数RowOfSquareTexts内使用@Builder装饰的函数SquareText
52  @Builder RowOfSquareTexts(label1: string, label2: string) {
53    Row() {
54      this.SquareText(label1)
55      this.SquareText(label2)
56    }
57    .width(2 * this.size1)
58    .height(1 * this.size1)
59  }
60
61  build() {
62    Column() {
63      Row() {
64        this.SquareText("A")
65        this.SquareText("B")
66      }
67      .width(2 * this.size1)
68      .height(1 * this.size1)
69
70      this.RowOfSquareTexts("C", "D")
71      Column() {
72        // 使用三次@Builder装饰的自定义组件
73        this.CompC(this.CompValue1)
74        this.CompC(this.CompValue2)
75        this.CompC(this.CompValue3)
76      }
77      .width(2 * this.size1)
78      .height(2 * this.size1)
79    }
80    .width(2 * this.size1)
81    .height(2 * this.size1)
82  }
83}
84```
85![builder](figures/builder.PNG)
86
87## @BuilderParam<sup>8+<sup>
88
89@BuilderParam装饰器用于修饰自定义组件内函数类型的属性(例如:`@BuilderParam noParam: () => void`),并且在初始化自定义组件时被@BuilderParam修饰的属性必须赋值。
90
91### 引入动机
92
93当开发者创建自定义组件,并想对该组件添加特定功能时(例如在自定义组件中添加一个点击跳转操作)。若直接在组件内嵌入事件方法,将会导致所有引入该自定义组件的地方均增加了该功能。为解决此问题,引入了@BuilderParam装饰器,此装饰器修饰的属性值可为@Builder装饰的函数,开发者可在初始化自定义组件时对此属性进行赋值,为自定义组件增加特定的功能。
94
95### 参数初始化组件
96
97通过参数初始化组件时,将@Builder装饰的函数赋值给@BuilderParam修饰的属性,并在自定义组件内调用该属性值。若@BuilderParam修饰的属性在进行赋值时不带参数(如:`noParam: this.specificNoParam`),则此属性的类型需定义为无返回值的函数(如:`@BuilderParam noParam: () => void`);若带参数(如:`withParam: this.SpecificWithParam('WithParamA')`),则此属性的类型需定义成any(如:`@BuilderParam withParam: any`)。
98
99```ts
100// xxx.ets
101@Component
102struct CustomContainer {
103  header: string = ''
104  @BuilderParam noParam: () => void
105  @BuilderParam withParam: any
106  footer: string = ''
107
108  build() {
109    Column() {
110      Text(this.header)
111        .fontSize(30)
112      this.noParam()
113      this.withParam()
114      Text(this.footer)
115        .fontSize(30)
116    }
117  }
118}
119
120@Entry
121@Component
122struct CustomContainerUser {
123  @Builder specificNoParam() {
124    Column() {
125      Text('noParam').fontSize(30)
126    }
127  }
128
129  @Builder SpecificWithParam(label: string) {
130    Column() {
131      Text(label).fontSize(30)
132    }
133  }
134
135  build() {
136    Column() {
137      CustomContainer({
138        header: 'HeaderA',
139        noParam: this.specificNoParam,
140        withParam: this.SpecificWithParam('WithParamA'),
141        footer: 'FooterA'
142      })
143      Divider()
144        .strokeWidth(3)
145        .margin(10)
146      CustomContainer({
147        header: 'HeaderB',
148        noParam: this.specificNoParam,
149        withParam: this.SpecificWithParam('WithParamB'),
150        footer: 'FooterB'
151      })
152    }
153  }
154}
155```
156
157![builder1](figures/builder1.PNG)
158
159### 尾随闭包初始化组件
160
161在自定义组件中使用@BuilderParam修饰的属性时也可通过尾随闭包进行初始化(在初始化自定义组件时,组件后紧跟一个大括号“{}”形成尾随闭包场景(`CustomContainer(){}`)。开发者可把尾随闭包看做一个容器,向其中填充内容,如在闭包内增加组件(`{Column(){...}`),闭包内语法规范与build函数一致。此场景下自定义组件内有且仅有一个使用@BuilderParam修饰的属性。
162
163示例:在闭包内添加Column组件并设置点击事件,在Column组件内调用@Builder修饰的specificParam函数,点击Column组件后将自定义组件CustomContainer中header的属性值由“header”改变为“changeHeader”。在初始化自定义组件CustomContainer时,尾随闭包的内容会被赋值给@BuilderParam修饰的closer属性。
164
165```ts
166// xxx.ets
167@Component
168struct CustomContainer {
169  header: string = ''
170  @BuilderParam closer: () => void
171
172  build() {
173    Column() {
174      Text(this.header)
175        .fontSize(30)
176      this.closer()
177    }
178  }
179}
180
181@Builder function specificParam(label1: string, label2: string) {
182  Column() {
183    Text(label1)
184      .fontSize(30)
185    Text(label2)
186      .fontSize(30)
187  }
188}
189
190@Entry
191@Component
192struct CustomContainerUser {
193  @State text: string = 'header'
194
195  build() {
196    Column() {
197      CustomContainer({
198        header: this.text,
199      }) {
200        Column() {
201          specificParam('testA', 'testB')
202        }.backgroundColor(Color.Yellow)
203        .onClick(() => {
204          this.text = 'changeHeader'
205        })
206      }
207    }
208  }
209}
210```
211
212![builder2](figures/builder2.gif)
213
214## @Styles
215
216ArkTS为了避免开发者对重复样式的设置,通过@Styles装饰器可以将多个样式设置提炼成一个方法,直接在组件声明时调用,通过@Styles装饰器可以快速定义并复用自定义样式。当前@Styles仅支持通用属性。
217
218@Styles可以定义在组件内或组件外,在组件外定义时需在方法名前面添加function关键字,组件内定义时则不需要添加function关键字。
219
220```ts
221// xxx.ets
222@Styles function globalFancy () {
223  .width(150)
224  .height(100)
225  .backgroundColor(Color.Pink)
226}
227
228@Entry
229@Component
230struct FancyUse {
231  @Styles componentFancy() {
232    .width(100)
233    .height(200)
234    .backgroundColor(Color.Yellow)
235  }
236
237  build() {
238    Column({ space: 10 }) {
239      Text('FancyA')
240        .globalFancy()
241        .fontSize(30)
242      Text('FancyB')
243        .globalFancy()
244        .fontSize(20)
245      Text('FancyC')
246        .componentFancy()
247        .fontSize(30)
248      Text('FancyD')
249        .componentFancy()
250        .fontSize(20)
251    }
252  }
253}
254```
255
256![styles](figures/styles.PNG)
257
258@Styles还可以在[StateStyles](../reference/arkui-ts/ts-universal-attributes-polymorphic-style.md)属性内部使用,在组件处于不同的状态时赋予相应的属性。
259
260在StateStyles内可以直接调用组件外定义的@Styles方法,但需要通过this关键字调用组件内定义的@Styles方法。
261
262```ts
263// xxx.ets
264@Styles function globalFancy () {
265  .width(120)
266  .height(120)
267  .backgroundColor(Color.Green)
268}
269
270@Entry
271@Component
272struct FancyUse {
273  @Styles componentFancy() {
274    .width(80)
275    .height(80)
276    .backgroundColor(Color.Red)
277  }
278
279  build() {
280    Row({ space: 10 }) {
281      Button('Fancy')
282        .stateStyles({
283          normal: {
284            .width(100)
285            .height(100)
286            .backgroundColor(Color.Blue)
287          },
288          disabled: this.componentFancy,
289          pressed: globalFancy
290        })
291    }
292  }
293}
294```
295
296![styles1](figures/styles1.gif)
297
298## @Extend
299
300@Extend装饰器将新的属性方法添加到Text、Column、Button等内置组件上,通过@Extend装饰器可以快速地扩展原生组件。@Extend不能定义在自定义组件struct内。
301
302```ts
303// xxx.ets
304@Extend(Text) function fancy (fontSize: number) {
305  .fontColor(Color.Red)
306  .fontSize(fontSize)
307  .fontStyle(FontStyle.Italic)
308  .fontWeight(600)
309}
310
311@Entry
312@Component
313struct FancyUse {
314  build() {
315    Row({ space: 10 }) {
316      Text("Fancy")
317        .fancy(16)
318      Text("Fancy")
319        .fancy(24)
320      Text("Fancy")
321        .fancy(32)
322    }
323  }
324}
325
326```
327
328> **说明:**
329>
330> - @Extend装饰器不能定义在自定义组件struct内。
331> - @Extend装饰器内仅支持属性方法设置。
332
333![extend](figures/extend.PNG)
334
335## @CustomDialog
336
337@CustomDialog装饰器用于装饰自定义弹窗组件,使得弹窗可以动态设置内容及样式。
338
339```ts
340// xxx.ets
341@CustomDialog
342struct DialogExample {
343  controller: CustomDialogController
344  action: () => void
345
346  build() {
347    Row() {
348      Button('Close CustomDialog')
349        .onClick(() => {
350          this.controller.close()
351          this.action()
352        })
353    }.padding(20)
354  }
355}
356
357@Entry
358@Component
359struct CustomDialogUser {
360  dialogController: CustomDialogController = new CustomDialogController({
361    builder: DialogExample({ action: this.onAccept }),
362    cancel: this.existApp,
363    autoCancel: true
364  });
365
366  onAccept() {
367    console.info('onAccept');
368  }
369
370  existApp() {
371    console.info('Cancel dialog!');
372  }
373
374  build() {
375    Column() {
376      Button('Click to open Dialog')
377        .onClick(() => {
378          this.dialogController.open()
379        })
380    }
381  }
382}
383```
384
385![customdialog](figures/customDialog.gif)