1# BuilderNode 2 3The **BuilderNode** module provides APIs for a BuilderNode – a custom node that can be used to mount native components. A BuilderNode can be used only as a leaf node. For details, see [BuilderNode Development](../../ui/arkts-user-defined-arktsNode-builderNode.md). 4 5> **NOTE** 6> 7> The initial APIs of this module are supported since API version 11. Newly added APIs will be marked with a superscript to indicate their earliest API version. 8> 9> **BuilderNode** is not available in DevEco Studio Previewer. 10 11## Modules to Import 12 13```ts 14import { BuilderNode, RenderOptions, NodeRenderType } from "@kit.ArkUI"; 15``` 16 17## NodeRenderType 18 19Enumerates the node rendering types. 20 21**Atomic service API**: This API can be used in atomic services since API version 12. 22 23**System capability**: SystemCapability.ArkUI.ArkUI.Full 24 25| Name | Value | Description | 26| ------------------- | --- | ---------------------------- | 27| RENDER_TYPE_DISPLAY | 0 | The node is displayed on the screen.| 28| RENDER_TYPE_TEXTURE | 1 | The node is exported as a texture. | 29 30> **NOTE** 31> 32> Currently, the **RENDER_TYPE_TEXTURE** type takes effect only for the [XComponentNode](./js-apis-arkui-xcomponentNode.md) and the [BuilderNode](#buildernode-1) holding a component tree whose root node is a custom component. 33> 34> In the case of [BuilderNode](#buildernode-1), the following custom components that function as the root node support texture export: Badge, Blank, Button, CanvasGradient, CanvasPattern, CanvasRenderingContext2D, Canvas, CheckboxGroup, Checkbox, Circle, ColumnSplit, Column, ContainerSpan, Counter, DataPanel, Divider, Ellipse, Flex, Gauge, Hyperlink, ImageBitmap, ImageData, Image, Line, LoadingProgress, Marquee, Matrix2D, OffscreenCanvasRenderingContext2D, OffscreenCanvas, Path2D, Path, PatternLock, Polygon, Polyline, Progress, QRCode, Radio, Rating, Rect, RelativeContainer, RowSplit, Row, Shape, Slider, Span, Stack, TextArea, TextClock, TextInput, TextTimer, Text, Toggle, Video (not supporting the native full-screen mode), Web, XComponent 35> 36> The following components support texture export since API version 12: DatePicker, ForEach, Grid, IfElse, LazyForEach, List, Scroll, Swiper, TimePicker, @Component decorated custom components, NodeContainer, and FrameNode and RenderNode mounted to a NodeContainer. 37> 38> For details, see [Rendering and Drawing Video and Button Components at the Same Layer](../../web/web-same-layer.md). 39 40## RenderOptions 41 42Provides optional parameters for creating a BuilderNode. 43 44**Atomic service API**: This API can be used in atomic services since API version 12. 45 46**System capability**: SystemCapability.ArkUI.ArkUI.Full 47 48| Name | Type | Mandatory| Description | 49| ------------- | -------------------------------------- | ---- | ------------------------------------------------------------ | 50| selfIdealSize | [Size](js-apis-arkui-graphics.md#size) | No | Ideal size of the node. | 51| type | [NodeRenderType](#noderendertype) | No | Rendering type of the node. | 52| surfaceId | string | No | Surface ID of the texture receiver. Generally, the texture receiver is an [OH_NativeImage](../apis-arkgraphics2d/_o_h___native_image.md#oh_nativeimage) instance.| 53 54## BuilderNode 55 56class BuilderNode\<Args extends Object[]> 57 58Implements a BuilderNode, which can create a component tree through the stateless UI method [@Builder](../../quick-start/arkts-builder.md) and hold the root node of the component tree. A BuilderNode cannot be defined as a state variable. The FrameNode held in the BuilderNode is only used to mount the BuilderNode to other FrameNodes as a child node. Undefined behavior may occur if you set attributes or perform operations on subnodes of the FrameNode held by the BuilderNode. Therefore, after you have obtained a [RenderNode](js-apis-arkui-renderNode.md#rendernode) through the [getFrameNode](#getframenode) method of the BuilderNode and the [getRenderNode](js-apis-arkui-frameNode.md#getrendernode) method of the [FrameNode](js-apis-arkui-frameNode.md#framenode), avoid setting the attributes or operating the subnodes through APIs of the [RenderNode](js-apis-arkui-renderNode.md#rendernode). 59 60**Atomic service API**: This API can be used in atomic services since API version 12. 61 62**System capability**: SystemCapability.ArkUI.ArkUI.Full 63 64### constructor 65 66constructor(uiContext: UIContext, options?: RenderOptions) 67 68Constructor for creating a BuilderNode. When the content generated by the BuilderNode is embedded in another RenderNode for display, that is, the RenderNode corresponding to the BuilderNode is mounted to another RenderNode for display, **selfIdealSize** in **RenderOptions** must be explicitly specified. If **selfIdealSize** is not set, the node in the builder follows the default parent component layout constraint [0, 0], which means that the size of the root node of the subtree in BuilderNode is [0, 0]. 69 70**Atomic service API**: This API can be used in atomic services since API version 12. 71 72**System capability**: SystemCapability.ArkUI.ArkUI.Full 73 74| Name | Type | Mandatory| Description | 75| --------- | --------------------------------------- | ---- | ----------------------------------------------------------------- | 76| uiContext | [UIContext](js-apis-arkui-UIContext.md) | Yes | UI context. For details about how to obtain it, see [Obtaining UI Context](./js-apis-arkui-node.md#obtaining-ui-context).| 77| options | [RenderOptions](#renderoptions) | No | Parameters for creating a BuilderNode. Default value: **undefined** | 78 79> **NOTE** 80> The input parameter for **uiContext** must be a valid value, that is, the UI context must be correct. If an invalid value is passed in or if no value is specified, creation will fail. 81 82### build 83 84build(builder: WrappedBuilder\<Args>, arg?: Object): void 85 86Creates a component tree based on the passed object and holds the root node of the component tree. The stateless UI method [@Builder](../../quick-start/arkts-builder.md) has at most one root node. 87Custom components are allowed. Yet, the custom components cannot use decorators, such as [@Reusable](../../quick-start/arkts-create-custom-components.md#basic-structure-of-a-custom-component), @Link, @Provide, and @Consume, for state synchronization with the page to which the BuilderNode is mounted. 88 89> **NOTE** 90> 91> When nesting @Builder, ensure that the input objects for the inner and outer @Builder methods are consistent. 92> 93> The outermost @Builder supports only one input argument. 94> 95> To operate objects in a BuilderNode, ensure that the reference to the BuilderNode is not garbage collected. Once a BuilderNode object is collected by the virtual machine, its FrameNode and RenderNode objects will also be dereferenced from the backend nodes. This means that any FrameNode objects obtained from a BuilderNode will no longer correspond to any actual node if the BuilderNode is garbage collected. 96 97**Atomic service API**: This API can be used in atomic services since API version 12. 98 99**System capability**: SystemCapability.ArkUI.ArkUI.Full 100 101**Parameters** 102 103| Name | Type | Mandatory| Description | 104| ------- | --------------------------------------------------------------- | ---- | -------------------------------------------------------------------------------------- | 105| builder | [WrappedBuilder\<Args>](../../quick-start/arkts-wrapBuilder.md) | Yes | Stateless UI method [@Builder](../../quick-start/arkts-builder.md) required for creating a component tree.| 106| arg | Object | No | Argument of the builder. Only one input argument is supported, and the type of the input argument must be consistent with the type defined by @Builder. | 107 108 109### BuildOptions<sup>12+</sup> 110 111Defines the optional build options. 112 113**Atomic service API**: This API can be used in atomic services since API version 12. 114 115**System capability**: SystemCapability.ArkUI.ArkUI.Full 116 117| Name | Type | Mandatory| Description | 118| ------------- | -------------------------------------- | ---- | ------------------------------------------------------------ | 119| nestingBuilderSupported |boolean | No | Whether to support nesting **@Builder** within **@Builder**. The value **false** means that the input arguments for **@Builder** are consistent, and **true** means the opposite.<br> Default value: **false** | 120 121### build<sup>12+</sup> 122 123build(builder: WrappedBuilder\<Args>, arg: Object, options: [BuildOptions](#buildoptions12)): void 124 125Creates a component tree based on the passed object and holds the root node of the component tree. The stateless UI method [@Builder](../../quick-start/arkts-builder.md) has at most one root node. 126Custom components are allowed. Yet, the custom components cannot use decorators, such as [@Reusable](../../quick-start/arkts-create-custom-components.md#basic-structure-of-a-custom-component), @Link, @Provide, and @Consume, for state synchronization with the owning page. 127 128> **NOTE** 129> 130> For details about the creation and update using @Builder, see [@Builder](../../quick-start/arkts-builder.md). 131> 132> The outermost @Builder supports only one input argument. 133 134**Atomic service API**: This API can be used in atomic services since API version 12. 135 136**System capability**: SystemCapability.ArkUI.ArkUI.Full 137 138**Parameters** 139 140| Name | Type | Mandatory| Description | 141| ------- | --------------------------------------------------------------- | ---- | -------------------------------------------------------------------------------------- | 142| builder | [WrappedBuilder\<Args>](../../quick-start/arkts-wrapBuilder.md) | Yes | Stateless UI method [@Builder](../../quick-start/arkts-builder.md) required for creating a component tree. | 143| arg | Object | Yes | Argument of the builder. Only one input argument is supported, and the type of the input argument must be consistent with the type defined by @Builder. | 144| options | BuildOptions | Yes | Build options, which determine whether to support nesting **@Builder** within **@Builder**. | 145 146**Example** 147```ts 148import { BuilderNode, NodeContent } from "@kit.ArkUI"; 149 150interface ParamsInterface { 151 text: string; 152 func: Function; 153} 154 155@Builder 156function buildTextWithFunc(fun: Function) { 157 Text(fun()) 158 .fontSize(50) 159 .fontWeight(FontWeight.Bold) 160 .margin({ bottom: 36 }) 161} 162 163@Builder 164function buildText(params: ParamsInterface) { 165 Column() { 166 Text(params.text) 167 .fontSize(50) 168 .fontWeight(FontWeight.Bold) 169 .margin({ bottom: 36 }) 170 buildTextWithFunc(params.func) 171 } 172} 173 174 175@Entry 176@Component 177struct Index { 178 @State message: string = "HELLO" 179 private content: NodeContent = new NodeContent(); 180 181 build() { 182 Row() { 183 Column() { 184 Button('addBuilderNode') 185 .onClick(() => { 186 let buildNode = new BuilderNode<[ParamsInterface]>(this.getUIContext()); 187 buildNode.build(wrapBuilder<[ParamsInterface]>(buildText), { 188 text: this.message, func: () => { 189 return "FUNCTION" 190 } 191 }, { nestingBuilderSupported: true }); 192 this.content.addFrameNode(buildNode.getFrameNode()); 193 buildNode.dispose(); 194 }) 195 ContentSlot(this.content) 196 } 197 .id("column") 198 .width('100%') 199 .height('100%') 200 } 201 .height('100%') 202 } 203} 204``` 205 206 207### getFrameNode 208 209getFrameNode(): FrameNode | null 210 211Obtains the FrameNode in the BuilderNode. The FrameNode is generated only after the BuilderNode executes the build operation. 212 213**Atomic service API**: This API can be used in atomic services since API version 12. 214 215**System capability**: SystemCapability.ArkUI.ArkUI.Full 216 217**Return value** 218 219| Type | Description | 220| --------------------------------------------------------- | --------------------------------------------------------------------- | 221| [FrameNode](js-apis-arkui-frameNode.md#framenode) \| null | **FrameNode** object. If no such object is held by the **BuilderNode** instance, null is returned.| 222 223**Example 1** 224 225In this example, the BuilderNode is returned as the root node of the **NodeContainer**. 226 227```ts 228import { NodeController, BuilderNode, FrameNode, UIContext } from "@kit.ArkUI"; 229 230class Params { 231 text: string = "" 232 constructor(text: string) { 233 this.text = text; 234 } 235} 236 237@Builder 238function buildText(params: Params) { 239 Column() { 240 Text(params.text) 241 .fontSize(50) 242 .fontWeight(FontWeight.Bold) 243 .margin({bottom: 36}) 244 } 245} 246 247class TextNodeController extends NodeController { 248 private textNode: BuilderNode<[Params]> | null = null; 249 private message: string = "DEFAULT"; 250 251 constructor(message: string) { 252 super(); 253 this.message = message; 254 } 255 256 makeNode(context: UIContext): FrameNode | null { 257 this.textNode = new BuilderNode(context); 258 this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message)) 259 260 return this.textNode.getFrameNode(); 261 } 262} 263 264@Entry 265@Component 266struct Index { 267 @State message: string = "hello" 268 269 build() { 270 Row() { 271 Column() { 272 NodeContainer(new TextNodeController(this.message)) 273 .width('100%') 274 .height(100) 275 .backgroundColor('#FFF0F0F0') 276 } 277 .width('100%') 278 .height('100%') 279 } 280 .height('100%') 281 } 282} 283``` 284 285**Example 2** 286 287This example shows how to mount a FrameNode within a BuilderNode to another FrameNode. 288 289```ts 290import { NodeController, BuilderNode, FrameNode, UIContext } from "@kit.ArkUI"; 291 292class Params { 293 text: string = "" 294 295 constructor(text: string) { 296 this.text = text; 297 } 298} 299 300@Builder 301function buildText(params: Params) { 302 Column() { 303 Text(params.text) 304 .fontSize(50) 305 .fontWeight(FontWeight.Bold) 306 .margin({ bottom: 36 }) 307 } 308} 309 310class TextNodeController extends NodeController { 311 private rootNode: FrameNode | null = null; 312 private textNode: BuilderNode<[Params]> | null = null; 313 private message: string = "DEFAULT"; 314 315 constructor(message: string) { 316 super(); 317 this.message = message; 318 } 319 320 makeNode(context: UIContext): FrameNode | null { 321 this.rootNode = new FrameNode(context); 322 this.textNode = new BuilderNode(context, { selfIdealSize: { width: 150, height: 150 } }); 323 this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message)); 324 if (this.rootNode !== null) { 325 this.rootNode.appendChild(this.textNode?.getFrameNode()); 326 } 327 328 return this.rootNode; 329 } 330} 331 332@Entry 333@Component 334struct Index { 335 @State message: string = "hello" 336 337 build() { 338 Row() { 339 Column() { 340 NodeContainer(new TextNodeController(this.message)) 341 .width('100%') 342 .height(100) 343 .backgroundColor('#FFF0F0F0') 344 } 345 .width('100%') 346 .height('100%') 347 } 348 .height('100%') 349 } 350} 351``` 352 353**Example 3** 354 355This example shows how to mount a BuilderNode's RenderNode under another RenderNode. Since the RenderNode does not pass layout constraints, this mode of mounting nodes is not recommended. 356 357```ts 358import { NodeController, BuilderNode, FrameNode, UIContext, RenderNode } from "@kit.ArkUI"; 359 360class Params { 361 text: string = "" 362 363 constructor(text: string) { 364 this.text = text; 365 } 366} 367 368@Builder 369function buildText(params: Params) { 370 Column() { 371 Text(params.text) 372 .fontSize(50) 373 .fontWeight(FontWeight.Bold) 374 .margin({ bottom: 36 }) 375 } 376} 377 378class TextNodeController extends NodeController { 379 private rootNode: FrameNode | null = null; 380 private textNode: BuilderNode<[Params]> | null = null; 381 private message: string = "DEFAULT"; 382 383 constructor(message: string) { 384 super(); 385 this.message = message; 386 } 387 388 makeNode(context: UIContext): FrameNode | null { 389 this.rootNode = new FrameNode(context); 390 let renderNode = new RenderNode(); 391 renderNode.clipToFrame = false; 392 this.textNode = new BuilderNode(context, { selfIdealSize: { width: 150, height: 150 } }); 393 this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message)); 394 const textRenderNode = this.textNode?.getFrameNode()?.getRenderNode(); 395 396 const rootRenderNode = this.rootNode.getRenderNode(); 397 if (rootRenderNode !== null) { 398 rootRenderNode.appendChild(renderNode); 399 renderNode.appendChild(textRenderNode); 400 } 401 402 return this.rootNode; 403 } 404} 405 406@Entry 407@Component 408struct Index { 409 @State message: string = "hello" 410 411 build() { 412 Row() { 413 Column() { 414 NodeContainer(new TextNodeController(this.message)) 415 .width('100%') 416 .height(100) 417 .backgroundColor('#FFF0F0F0') 418 } 419 .width('100%') 420 .height('100%') 421 } 422 .height('100%') 423 } 424} 425``` 426 427### update 428 429update(arg: Object): void 430 431Updates this BuilderNode based on the provided parameter, which is of the same type as the input parameter passed to the [build](#build) API. To call this API on a custom component, the variable used in the component must be defined as the @Prop type. 432 433**Atomic service API**: This API can be used in atomic services since API version 12. 434 435**System capability**: SystemCapability.ArkUI.ArkUI.Full 436 437**Parameters** 438 439| Name| Type | Mandatory| Description | 440| ------ | ------ | ---- | ------------------------------------------------------------------------ | 441| arg | Object | Yes | Parameter used to update the BuilderNode. It is of the same type as the parameter passed to the [build](#build) API.| 442 443**Example** 444```ts 445import { NodeController, BuilderNode, FrameNode, UIContext } from "@kit.ArkUI"; 446 447class Params { 448 text: string = "" 449 constructor(text: string) { 450 this.text = text; 451 } 452} 453 454// Custom component 455@Component 456struct TextBuilder { 457 @Prop message: string = "TextBuilder"; 458 459 build() { 460 Row() { 461 Column() { 462 Text(this.message) 463 .fontSize(50) 464 .fontWeight(FontWeight.Bold) 465 .margin({bottom: 36}) 466 .backgroundColor(Color.Gray) 467 } 468 } 469 } 470} 471 472@Builder 473function buildText(params: Params) { 474 Column() { 475 Text(params.text) 476 .fontSize(50) 477 .fontWeight(FontWeight.Bold) 478 .margin({ bottom: 36 }) 479 TextBuilder({message: params.text}) // Custom component 480 } 481} 482 483class TextNodeController extends NodeController { 484 private rootNode: FrameNode | null = null; 485 private textNode: BuilderNode<[Params]> | null = null; 486 private message: string = ""; 487 488 constructor(message: string) { 489 super() 490 this.message = message 491 } 492 493 makeNode(context: UIContext): FrameNode | null { 494 this.textNode = new BuilderNode(context); 495 this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message)) 496 return this.textNode.getFrameNode(); 497 } 498 499 update(message: string) { 500 if (this.textNode !== null) { 501 this.textNode.update(new Params(message)); 502 } 503 } 504} 505 506@Entry 507@Component 508struct Index { 509 @State message: string = "hello" 510 private textNodeController: TextNodeController = new TextNodeController(this.message); 511 private count = 0; 512 513 build() { 514 Row() { 515 Column() { 516 NodeContainer(this.textNodeController) 517 .width('100%') 518 .height(200) 519 .backgroundColor('#FFF0F0F0') 520 Button('Update') 521 .onClick(() => { 522 this.count += 1; 523 const message = "Update " + this.count.toString(); 524 this.textNodeController.update(message); 525 }) 526 } 527 .width('100%') 528 .height('100%') 529 } 530 .height('100%') 531 } 532} 533``` 534 535### postTouchEvent 536 537postTouchEvent(event: TouchEvent): boolean 538 539Posts a raw touch event to the FrameNode created by this BuilderNode. 540 541**postTouchEvent** dispatches the event from a middle node in the component tree downwards. To ensure the event is dispatched correctly, it needs to be transformed into the coordinate system of the parent component, as shown in the figure below. 542 543**OffsetA** indicates the offset of the BuildNode relative to the parent component. You can obtain this offset by calling [getPositionToParent](js-apis-arkui-frameNode.md#getpositiontoparent12) in the FrameNode. **OffsetB** indicates the offset of the touch point relative to the BuildNode. You can obtain this offset from the [TouchEvent](arkui-ts/ts-universal-events-touch.md#touchevent) object. **OffsetC** is the sum of **OffsetA** and **OffsetB**. It represents the final offset that you need to pass to **postTouchEvent**. 544 545 546 547> **NOTE** 548> 549> The coordinates you pass in need to be converted to pixel values (px). If the BuilderNode has any affine transformations applied to it, they must be taken into account and combined with the touch event coordinates. 550> 551> In [Webview](../apis-arkweb/js-apis-webview.md), coordinate system transformations are already handled internally, so you can directly dispatch the touch event without additional adjustments. 552> 553> The **postTouchEvent** API can be called only once for the same timestamp.<!--Del--> 554> 555> [UIExtensionComponent](arkui-ts/ts-container-ui-extension-component-sys.md) is not supported. 556<!--DelEnd--> 557 558**Atomic service API**: This API can be used in atomic services since API version 12. 559 560**System capability**: SystemCapability.ArkUI.ArkUI.Full 561 562**Parameters** 563 564| Name| Type | Mandatory| Description | 565| ------ | ------------------------------------------------------------------------- | ---- | ---------- | 566| event | [TouchEvent](arkui-ts/ts-universal-events-touch.md#touchevent) | Yes | Touch event.| 567 568**Return value** 569 570| Type | Description | 571| ------- | ------------------ | 572| boolean | Whether the event is successfully dispatched. The value **true** means the event is consumed by a component that responds to the event, and **false** means that no component responds to the event.<br>**NOTE**<br>If the event does not hit the expected component, ensure the following:<br>1. The coordinate system has been correctly transformed<br>2. The component is in an interactive state.<br>3. The event has been bound to the component.| 573 574**Example** 575 576```ts 577import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI'; 578 579class Params { 580 text: string = "this is a text" 581} 582 583@Builder 584function ButtonBuilder(params: Params) { 585 Column() { 586 Button(`button ` + params.text) 587 .borderWidth(2) 588 .backgroundColor(Color.Orange) 589 .width("100%") 590 .height("100%") 591 .gesture( 592 TapGesture() 593 .onAction((event: GestureEvent) => { 594 console.log("TapGesture"); 595 }) 596 ) 597 } 598 .width(500) 599 .height(300) 600 .backgroundColor(Color.Gray) 601} 602 603class MyNodeController extends NodeController { 604 private rootNode: BuilderNode<[Params]> | null = null; 605 private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(ButtonBuilder); 606 607 makeNode(uiContext: UIContext): FrameNode | null { 608 this.rootNode = new BuilderNode(uiContext); 609 this.rootNode.build(this.wrapBuilder, { text: "this is a string" }) 610 return this.rootNode.getFrameNode(); 611 } 612 613 // Coordinate system transformation 614 postTouchEvent(event: TouchEvent): boolean { 615 if (this.rootNode == null) { 616 return false; 617 } 618 let node: FrameNode | null = this.rootNode.getFrameNode(); 619 let offsetX: number | null | undefined = node?.getPositionToParent().x; 620 let offsetY: number | null | undefined = node?.getPositionToParent().y; 621 ; 622 let changedTouchLen = event.changedTouches.length; 623 for (let i = 0; i < changedTouchLen; i++) { 624 if (offsetX != null && offsetY != null && offsetX != undefined && offsetY != undefined) { 625 event.changedTouches[i].x = vp2px(offsetX + event.changedTouches[i].x); 626 event.changedTouches[i].y = vp2px(offsetY + event.changedTouches[i].y); 627 } 628 } 629 let result = this.rootNode.postTouchEvent(event); 630 console.log("result " + result); 631 return result; 632 } 633} 634 635@Entry 636@Component 637struct MyComponent { 638 private nodeController: MyNodeController = new MyNodeController(); 639 640 build() { 641 Column() { 642 NodeContainer(this.nodeController) 643 .height(300) 644 .width(500) 645 646 Column() 647 .width(500) 648 .height(300) 649 .backgroundColor(Color.Pink) 650 .onTouch((event) => { 651 if (event != undefined) { 652 this.nodeController.postTouchEvent(event); 653 } 654 }) 655 } 656 } 657} 658``` 659 660### dispose<sup>12+</sup> 661 662dispose(): void 663 664Releases this BuilderNode immediately. Calling **dispose** on a **BuilderNode** object breaks its reference to the backend entity node, and also simultaneously severs the references of its contained FrameNode and RenderNode to their respective entity nodes. 665 666**Atomic service API**: This API can be used in atomic services since API version 12. 667 668**System capability**: SystemCapability.ArkUI.ArkUI.Full 669 670```ts 671import { RenderNode, FrameNode, NodeController, BuilderNode } from "@kit.ArkUI"; 672 673@Component 674struct TestComponent { 675 build() { 676 Column() { 677 Text('This is a BuilderNode.') 678 .fontSize(16) 679 .fontWeight(FontWeight.Bold) 680 } 681 .width('100%') 682 .backgroundColor(Color.Gray) 683 } 684 685 aboutToAppear() { 686 console.error('aboutToAppear'); 687 } 688 689 aboutToDisappear() { 690 console.error('aboutToDisappear'); 691 } 692} 693 694@Builder 695function buildComponent() { 696 TestComponent() 697} 698 699class MyNodeController extends NodeController { 700 private rootNode: FrameNode | null = null; 701 private builderNode: BuilderNode<[]> | null = null; 702 703 makeNode(uiContext: UIContext): FrameNode | null { 704 this.rootNode = new FrameNode(uiContext); 705 this.builderNode = new BuilderNode(uiContext, { selfIdealSize: { width: 200, height: 100 } }); 706 this.builderNode.build(new WrappedBuilder(buildComponent)); 707 708 const rootRenderNode = this.rootNode!.getRenderNode(); 709 if (rootRenderNode !== null) { 710 rootRenderNode.size = { width: 200, height: 200 }; 711 rootRenderNode.backgroundColor = 0xff00ff00; 712 rootRenderNode.appendChild(this.builderNode!.getFrameNode()!.getRenderNode()); 713 } 714 715 return this.rootNode; 716 } 717 718 dispose() { 719 if (this.builderNode !== null) { 720 this.builderNode.dispose(); 721 } 722 } 723 724 removeBuilderNode() { 725 const rootRenderNode = this.rootNode!.getRenderNode(); 726 if (rootRenderNode !== null && this.builderNode !== null && this.builderNode.getFrameNode() !== null) { 727 rootRenderNode.removeChild(this.builderNode!.getFrameNode()!.getRenderNode()); 728 } 729 } 730} 731 732@Entry 733@Component 734struct Index { 735 private myNodeController: MyNodeController = new MyNodeController(); 736 737 build() { 738 Column({ space: 4 }) { 739 NodeContainer(this.myNodeController) 740 Button('BuilderNode dispose') 741 .onClick(() => { 742 this.myNodeController.removeBuilderNode(); 743 this.myNodeController.dispose(); 744 }) 745 .width('100%') 746 } 747 } 748} 749``` 750 751### reuse<sup>12+</sup> 752 753reuse(param?: Object): void 754 755Passes the reuse event to the custom component in this BuilderNode. 756 757**Atomic service API**: This API can be used in atomic services since API version 12. 758 759**System capability**: SystemCapability.ArkUI.ArkUI.Full 760 761**Parameters** 762 763| Name| Type | Mandatory| Description | 764| ------ | ------ | ---- | ------------------------------------------------------------------------ | 765| param | Object | No | Parameter used to reuse the BuilderNode. It is of the same type as the parameter passed to the [build](#build) API.| 766 767### recycle<sup>12+</sup> 768 769recycle(): void 770 771Passes the recycle event to the custom component in this BuilderNode. 772 773**Atomic service API**: This API can be used in atomic services since API version 12. 774 775**System capability**: SystemCapability.ArkUI.ArkUI.Full 776 777```ts 778import { FrameNode,NodeController,BuilderNode,UIContext } from "@kit.ArkUI"; 779 780class MyDataSource { 781 private dataArray: string[] = []; 782 private listener: DataChangeListener | null = null 783 784 public totalCount(): number { 785 return this.dataArray.length; 786 } 787 788 public getData(index: number) { 789 return this.dataArray[index]; 790 } 791 792 public pushData(data: string) { 793 this.dataArray.push(data); 794 } 795 796 public reloadListener(): void { 797 this.listener?.onDataReloaded(); 798 } 799 800 public registerDataChangeListener(listener: DataChangeListener): void { 801 this.listener = listener; 802 } 803 804 public unregisterDataChangeListener(): void { 805 this.listener = null; 806 } 807} 808 809class Params { 810 item: string = ''; 811 812 constructor(item: string) { 813 this.item = item; 814 } 815} 816 817@Builder 818function buildNode(param: Params = new Params("hello")) { 819 ReusableChildComponent2({ item: param.item }); 820} 821 822class MyNodeController extends NodeController { 823 public builderNode: BuilderNode<[Params]> | null = null; 824 public item: string = ""; 825 826 makeNode(uiContext: UIContext): FrameNode | null { 827 if (this.builderNode == null) { 828 this.builderNode = new BuilderNode(uiContext, { selfIdealSize: { width: 300, height: 200 } }); 829 this.builderNode.build(wrapBuilder<[Params]>(buildNode), new Params(this.item)); 830 } 831 return this.builderNode.getFrameNode(); 832 } 833} 834 835@Reusable 836@Component 837struct ReusableChildComponent { 838 @State item: string = ''; 839 private controller: MyNodeController = new MyNodeController(); 840 841 aboutToAppear() { 842 this.controller.item = this.item; 843 } 844 845 aboutToRecycle(): void { 846 console.log("ReusableChildComponent aboutToRecycle " + this.item); 847 this.controller?.builderNode?.recycle(); 848 } 849 850 aboutToReuse(params: object): void { 851 console.log("ReusableChildComponent aboutToReuse " + JSON.stringify(params)); 852 this.controller?.builderNode?.reuse(params); 853 } 854 855 build() { 856 NodeContainer(this.controller); 857 } 858} 859 860@Component 861struct ReusableChildComponent2 { 862 @Prop item: string = "false"; 863 864 aboutToReuse(params: Record<string, object>) { 865 console.log("ReusableChildComponent2 Reusable 2 " + JSON.stringify(params)); 866 } 867 868 aboutToRecycle(): void { 869 console.log("ReusableChildComponent2 aboutToRecycle 2 " + this.item); 870 } 871 872 build() { 873 Row() { 874 Text(this.item) 875 .fontSize(20) 876 .backgroundColor(Color.Yellow) 877 .margin({ left: 10 }) 878 }.margin({ left: 10, right: 10 }) 879 } 880} 881 882 883@Entry 884@Component 885struct Index { 886 @State data: MyDataSource = new MyDataSource(); 887 888 aboutToAppear() { 889 for (let i = 0;i < 100; i++) { 890 this.data.pushData(i.toString()); 891 } 892 } 893 894 build() { 895 Column() { 896 List({ space: 3 }) { 897 LazyForEach(this.data, (item: string) => { 898 ListItem() { 899 ReusableChildComponent({ item: item }) 900 } 901 }, (item: string) => item) 902 } 903 .width('100%') 904 .height('100%') 905 } 906 } 907} 908``` 909 910### updateConfiguration<sup>12+</sup> 911 912updateConfiguration(): void 913 914Updates the configuration of the entire node by passing in a [system environment change](../apis-ability-kit/js-apis-app-ability-configuration.md) event. 915 916**Atomic service API**: This API can be used in atomic services since API version 12. 917 918**System capability**: SystemCapability.ArkUI.ArkUI.Full 919 920> **NOTE** 921> 922> The **updateConfiguration** API is used to instruct an object to update, with the system environment used for the update being determined by the changes in the application's current system environment. 923 924**Example** 925```ts 926import { NodeController, BuilderNode, FrameNode, UIContext } from "@kit.ArkUI"; 927import { AbilityConstant, Configuration, EnvironmentCallback } from '@kit.AbilityKit'; 928 929class Params { 930 text: string = "" 931 932 constructor(text: string) { 933 this.text = text; 934 } 935} 936 937// Custom component 938@Component 939struct TextBuilder { 940 // The @Prop decorated attribute is the attribute to be updated in the custom component. It is a basic attribute. 941 @Prop message: string = "TextBuilder"; 942 943 build() { 944 Row() { 945 Column() { 946 Text(this.message) 947 .fontSize(50) 948 .fontWeight(FontWeight.Bold) 949 .margin({ bottom: 36 }) 950 .fontColor($r(`app.color.text_color`)) 951 .backgroundColor($r(`app.color.start_window_background`)) 952 } 953 } 954 } 955} 956 957@Builder 958function buildText(params: Params) { 959 Column() { 960 Text(params.text) 961 .fontSize(50) 962 .fontWeight(FontWeight.Bold) 963 .margin({ bottom: 36 }) 964 .fontColor($r(`app.color.text_color`)) 965 TextBuilder({ message: params.text }) // Custom component 966 }.backgroundColor($r(`app.color.start_window_background`)) 967} 968 969class TextNodeController extends NodeController { 970 private textNode: BuilderNode<[Params]> | null = null; 971 private message: string = ""; 972 973 constructor(message: string) { 974 super() 975 this.message = message; 976 } 977 978 makeNode(context: UIContext): FrameNode | null { 979 return this.textNode?.getFrameNode() ? this.textNode?.getFrameNode() : null; 980 } 981 982 createNode(context: UIContext) { 983 this.textNode = new BuilderNode(context); 984 this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message)); 985 builderNodeMap.push(this.textNode); 986 } 987 988 deleteNode() { 989 let node = builderNodeMap.pop(); 990 node?.dispose(); 991 } 992 993 update(message: string) { 994 if (this.textNode !== null) { 995 // Call update to perform an update. 996 this.textNode.update(new Params(message)); 997 } 998 } 999} 1000 1001// Record the created custom node object. 1002const builderNodeMap: Array<BuilderNode<[Params]>> = new Array(); 1003 1004function updateColorMode() { 1005 builderNodeMap.forEach((value, index) => { 1006 // Notify BuilderNode of the environment changes. 1007 value.updateConfiguration(); 1008 }) 1009} 1010 1011@Entry 1012@Component 1013struct Index { 1014 @State message: string = "hello" 1015 private textNodeController: TextNodeController = new TextNodeController(this.message); 1016 private count = 0; 1017 1018 aboutToAppear(): void { 1019 let environmentCallback: EnvironmentCallback = { 1020 onMemoryLevel: (level: AbilityConstant.MemoryLevel): void => { 1021 console.log('onMemoryLevel'); 1022 }, 1023 onConfigurationUpdated: (config: Configuration): void => { 1024 console.log('onConfigurationUpdated ' + JSON.stringify(config)); 1025 updateColorMode(); 1026 } 1027 } 1028 // Register a callback. 1029 this.getUIContext().getHostContext()?.getApplicationContext().on('environment', environmentCallback); 1030 // Create a custom node and add it to the map. 1031 this.textNodeController.createNode(this.getUIContext()); 1032 } 1033 1034 aboutToDisappear(): void { 1035 // Remove the reference to the custom node from the map and release the node. 1036 this.textNodeController.deleteNode(); 1037 } 1038 1039 build() { 1040 Row() { 1041 Column() { 1042 NodeContainer(this.textNodeController) 1043 .width('100%') 1044 .height(200) 1045 .backgroundColor('#FFF0F0F0') 1046 Button('Update') 1047 .onClick(() => { 1048 this.count += 1; 1049 const message = "Update " + this.count.toString(); 1050 this.textNodeController.update(message); 1051 }) 1052 } 1053 .width('100%') 1054 .height('100%') 1055 } 1056 .height('100%') 1057 } 1058} 1059``` 1060