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 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 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 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 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 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 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