1# BuilderNode 2 3The **BuilderNode** module provides APIs for a BuilderNode – a custom node that can be used to mount built-in components. A BuilderNode can be used only as a leaf node. For details, see [BuilderNode Development](../../ui/arkts-user-defined-arktsNode-builderNode.md). For best practices, see [Dynamic Component Creation: Dynamically Adding, Updating, and Deleting Components](https://developer.huawei.com/consumer/en/doc/best-practices/bpta-ui-dynamic-operations#section153921947151012). 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> If you encounter display issues when reusing a BuilderNode across pages, see [Cross-Page Reuse Considerations](../../ui/arkts-user-defined-arktsNode-builderNode.md#cross-page-reuse-considerations) for guidance. 10> 11> **BuilderNode** is not available in DevEco Studio Previewer. 12> 13> Custom components under a BuilderNode can use the [@Prop](../../ui/state-management/arkts-prop.md) decorator. The [@Link](../../ui/state-management/arkts-link.md) decorator cannot be used to synchronize data and states across BuilderNode boundaries. 14> 15> If a BuilderNode contains custom components as child nodes, these custom components cannot use the [@Reusable](../../ui/state-management/arkts-reusable.md) decorator. For details, see [Using the @Reusable Decorator with BuilderNode Child Components](../../ui/arkts-user-defined-arktsNode-builderNode.md#using-the-reusable-decorator-with-buildernode-child-components). 16> 17> Since API version 12, custom components can receive [LocalStorage](../../ui/state-management/arkts-localstorage.md) instances. You can use LocalStorage related decorators such as [@LocalStorageProp](../../ui/state-management/arkts-localstorage.md#localstorageprop) and [@LocalStorageLink](../../ui/state-management/arkts-localstorage.md#localstoragelink) by [passing LocalStorage instances](../../ui/state-management/arkts-localstorage.md#example-of-providing-a-custom-component-with-access-to-a-localstorage-instance). 18> 19> Since API version 20, when configured with [BuildOptions](#buildoptions12), custom components within a BuilderNode can access the host page's [@Provide](../../ui/state-management/arkts-provide-and-consume.md) data through their [@Consume](../../ui/state-management/arkts-provide-and-consume.md) decorated attributes. 20> 21> The behavior of other decorators is undefined. Avoid using those decorators. 22 23 24## Modules to Import 25 26```ts 27import { BuilderNode, RenderOptions, NodeRenderType } from "@kit.ArkUI"; 28``` 29 30## NodeRenderType 31 32Enumerates the node rendering types. 33 34**Atomic service API**: This API can be used in atomic services since API version 12. 35 36**System capability**: SystemCapability.ArkUI.ArkUI.Full 37 38| Name | Value | Description | 39| ------------------- | --- | ---------------------------- | 40| RENDER_TYPE_DISPLAY | 0 | The node is displayed on the screen.| 41| RENDER_TYPE_TEXTURE | 1 | The node is exported as a texture. | 42 43> **NOTE** 44> 45> 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. 46> 47> 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 (without support for full-screen playback), Web, XComponent 48> 49> 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. 50> 51> For details, see [Rendering and Drawing Video and Button Components at the Same Layer](../../web/web-same-layer.md). 52 53## RenderOptions 54 55Provides optional parameters for creating a BuilderNode. 56 57**Atomic service API**: This API can be used in atomic services since API version 12. 58 59**System capability**: SystemCapability.ArkUI.ArkUI.Full 60 61| Name | Type | Mandatory| Description | 62| ------------- | -------------------------------------- | ---- | ------------------------------------------------------------ | 63| selfIdealSize | [Size](js-apis-arkui-graphics.md#size) | No | Ideal size of the node.<br>Default value: **{ width: 0, height: 0 }**.| 64| type | [NodeRenderType](#noderendertype) | No | Rendering type of the node.<br>Default value: **NodeRenderType.RENDER_TYPE_DISPLAY**.| 65| surfaceId | string | No | Surface ID of the texture receiver. Typically, the texture receiver is [OH_NativeImage](../apis-arkgraphics2d/capi-oh-nativeimage-oh-nativeimage.md).<br>This parameter is effective only when **type** is set to **NodeRenderType.RENDER_TYPE_TEXTURE**.<br>Default value: **""**.| 66 67## BuilderNode 68 69class BuilderNode\<Args extends Object[]> 70 71Implements a BuilderNode, which can create a component tree through the stateless UI method [@Builder](../../ui/state-management/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) 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), avoid setting the attributes or operating the subnodes through APIs of [RenderNode](js-apis-arkui-renderNode.md). 72 73**Atomic service API**: This API can be used in atomic services since API version 12. 74 75**System capability**: SystemCapability.ArkUI.ArkUI.Full 76 77### constructor 78 79constructor(uiContext: UIContext, options?: RenderOptions) 80 81Constructor 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]. 82 83**Atomic service API**: This API can be used in atomic services since API version 12. 84 85**System capability**: SystemCapability.ArkUI.ArkUI.Full 86 87| Name | Type | Mandatory| Description | 88| --------- | --------------------------------------- | ---- | ----------------------------------------------------------------- | 89| uiContext | [UIContext](arkts-apis-uicontext-uicontext.md) | Yes | UI context. For details about how to obtain it, see [Obtaining UI Context](./js-apis-arkui-node.md#obtaining-ui-context).| 90| options | [RenderOptions](#renderoptions) | No | Parameters for creating a BuilderNode. Default value: **undefined** | 91 92> **NOTE** 93> 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. 94 95### build 96 97build(builder: WrappedBuilder\<Args>, arg?: Object): void 98 99Creates a component tree based on the passed object and holds the root node of the component tree. The stateless UI method [@Builder](../../ui/state-management/arkts-builder.md) has at most one root node. 100Custom components are allowed. 101 102> **NOTE** 103> 104> When nesting @Builder, ensure that the input objects for the inner and outer @Builder methods are consistent. 105> 106> The outermost @Builder supports only one input parameter. 107> 108> The build parameter uses the pass-by-value semantics. To implement state updates, you must explicitly use the [update](#update) API. 109> 110> 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. 111> 112> The BuilderNode object maintains references to its underlying entity nodes. When the BuilderNode frontend object is no longer required for managing backend nodes, call the [dispose](#dispose12) API to release node references and unbind frontend and backend nodes. 113 114**Atomic service API**: This API can be used in atomic services since API version 12. 115 116**System capability**: SystemCapability.ArkUI.ArkUI.Full 117 118**Parameters** 119 120| Name | Type | Mandatory| Description | 121| ------- | --------------------------------------------------------------- | ---- | -------------------------------------------------------------------------------------- | 122| builder | [WrappedBuilder\<Args>](../../ui/state-management/arkts-wrapBuilder.md) | Yes | Stateless UI method [@Builder](../../ui/state-management/arkts-builder.md) required for creating a component tree.| 123| arg | Object | No | Argument of the builder. Only one input parameter is supported, and the type of the input parameter must be consistent with the type defined by @Builder.<br>Default value: **undefined**.| 124 125### build<sup>12+</sup> 126 127build(builder: WrappedBuilder\<Args>, arg: Object, options: [BuildOptions](#buildoptions12)): void 128 129Creates a component tree based on the passed object and holds the root node of the component tree. The stateless UI method [@Builder](../../ui/state-management/arkts-builder.md) has at most one root node. 130Custom components are allowed. 131 132> **NOTE** 133> 134> For details about the creation and update using @Builder, see [@Builder](../../ui/state-management/arkts-builder.md). 135> 136> The outermost @Builder supports only one input parameter. 137 138**Atomic service API**: This API can be used in atomic services since API version 12. 139 140**System capability**: SystemCapability.ArkUI.ArkUI.Full 141 142**Parameters** 143 144| Name | Type | Mandatory| Description | 145| ------- | --------------------------------------------------------------- | ---- | -------------------------------------------------------------------------------------- | 146| builder | [WrappedBuilder\<Args>](../../ui/state-management/arkts-wrapBuilder.md) | Yes | Stateless UI method [@Builder](../../ui/state-management/arkts-builder.md) required for creating a component tree. | 147| arg | Object | Yes | Argument of the builder. Only one input parameter is supported, and the type of the input parameter must be consistent with the type defined by @Builder. | 148| options | [BuildOptions](#buildoptions12) | Yes | Build options, which determine whether to support nesting **@Builder** within **@Builder**. | 149 150**Example** 151```ts 152import { BuilderNode, NodeContent } from "@kit.ArkUI"; 153 154interface ParamsInterface { 155 text: string; 156 func: Function; 157} 158 159@Builder 160function buildTextWithFunc(fun: Function) { 161 Text(fun()) 162 .fontSize(50) 163 .fontWeight(FontWeight.Bold) 164 .margin({ bottom: 36 }) 165} 166 167@Builder 168function buildText(params: ParamsInterface) { 169 Column() { 170 Text(params.text) 171 .fontSize(50) 172 .fontWeight(FontWeight.Bold) 173 .margin({ bottom: 36 }) 174 buildTextWithFunc(params.func) 175 } 176} 177 178 179@Entry 180@Component 181struct Index { 182 @State message: string = "HELLO"; 183 private content: NodeContent = new NodeContent(); 184 185 build() { 186 Row() { 187 Column() { 188 Button('addBuilderNode') 189 .onClick(() => { 190 let buildNode = new BuilderNode<[ParamsInterface]>(this.getUIContext()); 191 buildNode.build(wrapBuilder<[ParamsInterface]>(buildText), { 192 text: this.message, func: () => { 193 return "FUNCTION"; 194 } 195 }, { nestingBuilderSupported: true }); 196 this.content.addFrameNode(buildNode.getFrameNode()); 197 buildNode.dispose(); 198 }) 199 ContentSlot(this.content) 200 } 201 .id("column") 202 .width('100%') 203 .height('100%') 204 } 205 .height('100%') 206 } 207} 208``` 209 210### BuildOptions<sup>12+</sup> 211 212Defines the optional build options. 213 214**System capability**: SystemCapability.ArkUI.ArkUI.Full 215 216| Name | Type | Mandatory| Description | 217| ------------- | -------------------------------------- | ---- | ------------------------------------------------------------ | 218| 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**.<br>**Atomic service API**: This API can be used in atomic services since API version 12.| 219| localStorage<sup>20+</sup> |[LocalStorage](../../ui/state-management/arkts-localstorage.md) | No | LocalStorage for the current BuilderNode. Custom components mounted under this BuilderNode will share the specified LocalStorage. **NOTE**<br>If LocalStorage is also passed through a custom component's constructor, the constructor parameter takes precedence.<br> Default value: **null**.<br>**Atomic service API**: This API can be used in atomic services since API version 20.| 220| enableProvideConsumeCrossing<sup>20+</sup> | boolean | No| Whether @Consume in the custom components under the current BuilderNode can communicate with the host page's @Provide. The value **true** means that this feature is supported, and **false** means the opposite. Default value: **false**.<br>**Atomic service API**: This API can be used in atomic services since API version 20.| 221 222### InputEventType<sup>20+</sup> 223 224type InputEventType = TouchEvent | MouseEvent | AxisEvent 225 226Defines the parameter type for **postInputEvent**, which specifies the input event types that can be transmitted. 227 228**Atomic service API**: This API can be used in atomic services since API version 20. 229 230**System capability**: SystemCapability.ArkUI.ArkUI.Full 231 232| Type| Description | 233| ------------- | -------------------------------------- | 234| [TouchEvent](arkui-ts/ts-universal-events-touch.md#touchevent) | Touch event.| 235| [MouseEvent](arkui-ts/ts-universal-mouse-key.md#mouseevent) | Mouse event.| 236| [AxisEvent](arkui-ts/ts-universal-events-axis.md#axisevent) | Axis event.| 237 238### getFrameNode 239 240getFrameNode(): FrameNode | null 241 242Obtains the FrameNode in the BuilderNode. The FrameNode is generated only after the BuilderNode executes the build operation. 243 244**Atomic service API**: This API can be used in atomic services since API version 12. 245 246**System capability**: SystemCapability.ArkUI.ArkUI.Full 247 248**Return value** 249 250| Type | Description | 251| --------------------------------------------------------- | --------------------------------------------------------------------- | 252| [FrameNode](js-apis-arkui-frameNode.md) \| null | **FrameNode** object. If no such object is held by the **BuilderNode** instance, null is returned.| 253 254**Example 1** 255 256In this example, the BuilderNode is returned as the root node of the **NodeContainer**. 257 258```ts 259import { NodeController, BuilderNode, FrameNode, UIContext } from "@kit.ArkUI"; 260 261class Params { 262 text: string = ""; 263 constructor(text: string) { 264 this.text = text; 265 } 266} 267 268@Builder 269function buildText(params: Params) { 270 Column() { 271 Text(params.text) 272 .fontSize(50) 273 .fontWeight(FontWeight.Bold) 274 .margin({bottom: 36}) 275 } 276} 277 278class TextNodeController extends NodeController { 279 private textNode: BuilderNode<[Params]> | null = null; 280 private message: string = "DEFAULT"; 281 282 constructor(message: string) { 283 super(); 284 this.message = message; 285 } 286 287 makeNode(context: UIContext): FrameNode | null { 288 this.textNode = new BuilderNode(context); 289 this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message)); 290 291 return this.textNode.getFrameNode(); 292 } 293} 294 295@Entry 296@Component 297struct Index { 298 @State message: string = "hello"; 299 300 build() { 301 Row() { 302 Column() { 303 NodeContainer(new TextNodeController(this.message)) 304 .width('100%') 305 .height(100) 306 .backgroundColor('#FFF0F0F0') 307 } 308 .width('100%') 309 .height('100%') 310 } 311 .height('100%') 312 } 313} 314``` 315 316**Example 2** 317 318This example shows how to mount a FrameNode within a BuilderNode to another FrameNode. 319 320```ts 321import { NodeController, BuilderNode, FrameNode, UIContext } from "@kit.ArkUI"; 322 323class Params { 324 text: string = ""; 325 326 constructor(text: string) { 327 this.text = text; 328 } 329} 330 331@Builder 332function buildText(params: Params) { 333 Column() { 334 Text(params.text) 335 .fontSize(50) 336 .fontWeight(FontWeight.Bold) 337 .margin({ bottom: 36 }) 338 } 339} 340 341class TextNodeController extends NodeController { 342 private rootNode: FrameNode | null = null; 343 private textNode: BuilderNode<[Params]> | null = null; 344 private message: string = "DEFAULT"; 345 346 constructor(message: string) { 347 super(); 348 this.message = message; 349 } 350 351 makeNode(context: UIContext): FrameNode | null { 352 this.rootNode = new FrameNode(context); 353 this.textNode = new BuilderNode(context, { selfIdealSize: { width: 150, height: 150 } }); 354 this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message)); 355 if (this.rootNode !== null) { 356 this.rootNode.appendChild(this.textNode?.getFrameNode()); 357 } 358 359 return this.rootNode; 360 } 361} 362 363@Entry 364@Component 365struct Index { 366 @State message: string = "hello"; 367 368 build() { 369 Row() { 370 Column() { 371 NodeContainer(new TextNodeController(this.message)) 372 .width('100%') 373 .height(100) 374 .backgroundColor('#FFF0F0F0') 375 } 376 .width('100%') 377 .height('100%') 378 } 379 .height('100%') 380 } 381} 382``` 383 384**Example 3** 385 386This 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. 387 388```ts 389import { NodeController, BuilderNode, FrameNode, UIContext, RenderNode } from "@kit.ArkUI"; 390 391class Params { 392 text: string = ""; 393 394 constructor(text: string) { 395 this.text = text; 396 } 397} 398 399@Builder 400function buildText(params: Params) { 401 Column() { 402 Text(params.text) 403 .fontSize(50) 404 .fontWeight(FontWeight.Bold) 405 .margin({ bottom: 36 }) 406 } 407} 408 409class TextNodeController extends NodeController { 410 private rootNode: FrameNode | null = null; 411 private textNode: BuilderNode<[Params]> | null = null; 412 private message: string = "DEFAULT"; 413 414 constructor(message: string) { 415 super(); 416 this.message = message; 417 } 418 419 makeNode(context: UIContext): FrameNode | null { 420 this.rootNode = new FrameNode(context); 421 let renderNode = new RenderNode(); 422 renderNode.clipToFrame = false; 423 this.textNode = new BuilderNode(context, { selfIdealSize: { width: 150, height: 150 } }); 424 this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message)); 425 const textRenderNode = this.textNode?.getFrameNode()?.getRenderNode(); 426 427 const rootRenderNode = this.rootNode.getRenderNode(); 428 if (rootRenderNode !== null) { 429 rootRenderNode.appendChild(renderNode); 430 renderNode.appendChild(textRenderNode); 431 } 432 433 return this.rootNode; 434 } 435} 436 437@Entry 438@Component 439struct Index { 440 @State message: string = "hello"; 441 442 build() { 443 Row() { 444 Column() { 445 NodeContainer(new TextNodeController(this.message)) 446 .width('100%') 447 .height(100) 448 .backgroundColor('#FFF0F0F0') 449 } 450 .width('100%') 451 .height('100%') 452 } 453 .height('100%') 454 } 455} 456``` 457 458### update 459 460update(arg: Object): void 461 462Updates 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. 463 464**Atomic service API**: This API can be used in atomic services since API version 12. 465 466**System capability**: SystemCapability.ArkUI.ArkUI.Full 467 468**Parameters** 469 470| Name| Type | Mandatory| Description | 471| ------ | ------ | ---- | ------------------------------------------------------------------------ | 472| arg | Object | Yes | Parameter used to update the BuilderNode. It is of the same type as the parameter passed to the [build](#build) API.| 473 474**Example** 475```ts 476import { NodeController, BuilderNode, FrameNode, UIContext } from "@kit.ArkUI"; 477 478class Params { 479 text: string = ""; 480 constructor(text: string) { 481 this.text = text; 482 } 483} 484 485// Custom component 486@Component 487struct TextBuilder { 488 @Prop message: string = "TextBuilder"; 489 490 build() { 491 Row() { 492 Column() { 493 Text(this.message) 494 .fontSize(50) 495 .fontWeight(FontWeight.Bold) 496 .margin({bottom: 36}) 497 .backgroundColor(Color.Gray) 498 } 499 } 500 } 501} 502 503@Builder 504function buildText(params: Params) { 505 Column() { 506 Text(params.text) 507 .fontSize(50) 508 .fontWeight(FontWeight.Bold) 509 .margin({ bottom: 36 }) 510 TextBuilder({message: params.text}) // Custom component 511 } 512} 513 514class TextNodeController extends NodeController { 515 private rootNode: FrameNode | null = null; 516 private textNode: BuilderNode<[Params]> | null = null; 517 private message: string = ""; 518 519 constructor(message: string) { 520 super(); 521 this.message = message; 522 } 523 524 makeNode(context: UIContext): FrameNode | null { 525 this.textNode = new BuilderNode(context); 526 this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message)); 527 return this.textNode.getFrameNode(); 528 } 529 530 update(message: string) { 531 if (this.textNode !== null) { 532 this.textNode.update(new Params(message)); 533 } 534 } 535} 536 537@Entry 538@Component 539struct Index { 540 @State message: string = "hello"; 541 private textNodeController: TextNodeController = new TextNodeController(this.message); 542 private count = 0; 543 544 build() { 545 Row() { 546 Column() { 547 NodeContainer(this.textNodeController) 548 .width('100%') 549 .height(200) 550 .backgroundColor('#FFF0F0F0') 551 Button('Update') 552 .onClick(() => { 553 this.count += 1; 554 const message = "Update " + this.count.toString(); 555 this.textNodeController.update(message); 556 }) 557 } 558 .width('100%') 559 .height('100%') 560 } 561 .height('100%') 562 } 563} 564``` 565 566### postTouchEvent 567 568postTouchEvent(event: TouchEvent): boolean 569 570Posts a raw touch event to the FrameNode created by this BuilderNode. 571 572**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. 573 574**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**. 575 576 577 578> **NOTE** 579> 580> 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. 581> 582> In [Webview](../apis-arkweb/arkts-apis-webview.md), coordinate system transformations are already handled internally, so you can directly dispatch the touch event without additional adjustments. 583> 584> The **postTouchEvent** API can be called only once for the same timestamp.<!--Del--> 585> 586> [UIExtensionComponent](arkui-ts/ts-container-ui-extension-component-sys.md) is not supported. 587<!--DelEnd--> 588 589**Atomic service API**: This API can be used in atomic services since API version 12. 590 591**System capability**: SystemCapability.ArkUI.ArkUI.Full 592 593**Parameters** 594 595| Name| Type | Mandatory| Description | 596| ------ | ------------------------------------------------------------------------- | ---- | ---------- | 597| event | [TouchEvent](arkui-ts/ts-universal-events-touch.md#touchevent) | Yes | Touch event.| 598 599**Return value** 600 601| Type | Description | 602| ------- | ------------------ | 603| 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.| 604 605**Example** 606 607```ts 608import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI'; 609 610class Params { 611 text: string = "this is a text"; 612} 613 614@Builder 615function ButtonBuilder(params: Params) { 616 Column() { 617 Button(`button ` + params.text) 618 .borderWidth(2) 619 .backgroundColor(Color.Orange) 620 .width("100%") 621 .height("100%") 622 .gesture( 623 TapGesture() 624 .onAction((event: GestureEvent) => { 625 console.info("TapGesture"); 626 }) 627 ) 628 } 629 .width(500) 630 .height(300) 631 .backgroundColor(Color.Gray) 632} 633 634class MyNodeController extends NodeController { 635 private rootNode: BuilderNode<[Params]> | null = null; 636 private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(ButtonBuilder); 637 638 makeNode(uiContext: UIContext): FrameNode | null { 639 this.rootNode = new BuilderNode(uiContext); 640 this.rootNode.build(this.wrapBuilder, { text: "this is a string" }); 641 return this.rootNode.getFrameNode(); 642 } 643 644 // Coordinate system transformation 645 postTouchEvent(event: TouchEvent, uiContext: UIContext): boolean { 646 if (this.rootNode == null) { 647 return false; 648 } 649 let node: FrameNode | null = this.rootNode.getFrameNode(); 650 let offsetX: number | null | undefined = node?.getPositionToParent().x; 651 let offsetY: number | null | undefined = node?.getPositionToParent().y; 652 653 let changedTouchLen = event.changedTouches.length; 654 for (let i = 0; i < changedTouchLen; i++) { 655 if (offsetX != null && offsetY != null && offsetX != undefined && offsetY != undefined) { 656 event.changedTouches[i].x = uiContext.vp2px(offsetX + event.changedTouches[i].x); 657 event.changedTouches[i].y = uiContext.vp2px(offsetY + event.changedTouches[i].y); 658 } 659 } 660 let result = this.rootNode.postTouchEvent(event); 661 console.info("result " + result); 662 return result; 663 } 664} 665 666@Entry 667@Component 668struct MyComponent { 669 private nodeController: MyNodeController = new MyNodeController(); 670 671 build() { 672 Column() { 673 NodeContainer(this.nodeController) 674 .height(300) 675 .width(500) 676 677 Column() 678 .width(500) 679 .height(300) 680 .backgroundColor(Color.Pink) 681 .onTouch((event) => { 682 if (event != undefined) { 683 this.nodeController.postTouchEvent(event, this.getUIContext()); 684 } 685 }) 686 } 687 } 688} 689``` 690 691### dispose<sup>12+</sup> 692 693dispose(): void 694 695Immediately releases the reference relationship between this BuilderNode object and its [entity node](../../ui/arkts-user-defined-node.md#basic-concepts). For details about the scenarios involving BuilderNode unbinding, see [Canceling the Reference to the Entity Node](../../ui/arkts-user-defined-arktsNode-builderNode.md#canceling-the-reference-to-the-entity-node). 696 697> **NOTE** 698> 699> Failure to properly release BuilderNode objects may result in memory leaks. To avoid this, be sure to call **dispose()** on the BuilderNode when you no longer need it. This reduces the complexity of reference relationships and lowers the risk of memory leaks. 700 701**Atomic service API**: This API can be used in atomic services since API version 12. 702 703**System capability**: SystemCapability.ArkUI.ArkUI.Full 704 705```ts 706import { RenderNode, FrameNode, NodeController, BuilderNode } from "@kit.ArkUI"; 707 708@Component 709struct TestComponent { 710 build() { 711 Column() { 712 Text('This is a BuilderNode.') 713 .fontSize(16) 714 .fontWeight(FontWeight.Bold) 715 } 716 .width('100%') 717 .backgroundColor(Color.Gray) 718 } 719 720 aboutToAppear() { 721 console.info('aboutToAppear'); 722 } 723 724 aboutToDisappear() { 725 console.info('aboutToDisappear'); 726 } 727} 728 729@Builder 730function buildComponent() { 731 TestComponent() 732} 733 734class MyNodeController extends NodeController { 735 private rootNode: FrameNode | null = null; 736 private builderNode: BuilderNode<[]> | null = null; 737 738 makeNode(uiContext: UIContext): FrameNode | null { 739 this.rootNode = new FrameNode(uiContext); 740 this.builderNode = new BuilderNode(uiContext, { selfIdealSize: { width: 200, height: 100 } }); 741 this.builderNode.build(new WrappedBuilder(buildComponent)); 742 743 const rootRenderNode = this.rootNode!.getRenderNode(); 744 if (rootRenderNode !== null) { 745 rootRenderNode.size = { width: 200, height: 200 }; 746 rootRenderNode.backgroundColor = 0xff00ff00; 747 rootRenderNode.appendChild(this.builderNode!.getFrameNode()!.getRenderNode()); 748 } 749 750 return this.rootNode; 751 } 752 753 dispose() { 754 if (this.builderNode !== null) { 755 this.builderNode.dispose(); 756 } 757 } 758 759 removeBuilderNode() { 760 const rootRenderNode = this.rootNode!.getRenderNode(); 761 if (rootRenderNode !== null && this.builderNode !== null && this.builderNode.getFrameNode() !== null) { 762 rootRenderNode.removeChild(this.builderNode!.getFrameNode()!.getRenderNode()); 763 } 764 } 765} 766 767@Entry 768@Component 769struct Index { 770 private myNodeController: MyNodeController = new MyNodeController(); 771 772 build() { 773 Column({ space: 4 }) { 774 NodeContainer(this.myNodeController) 775 Button('BuilderNode dispose') 776 .onClick(() => { 777 this.myNodeController.removeBuilderNode(); 778 this.myNodeController.dispose(); 779 }) 780 .width('100%') 781 } 782 } 783} 784``` 785 786### reuse<sup>12+</sup> 787 788reuse(param?: Object): void 789 790Triggers component reuse for custom components under this BuilderNode. For details about component reuse, see [\@Reusable Decorator: Reusing Components](../../ui/state-management/arkts-reusable.md). For details about the scenarios involving BuilderNode unbinding, see [Canceling the Reference to the Entity Node](../../ui/arkts-user-defined-arktsNode-builderNode.md#canceling-the-reference-to-the-entity-node). 791 792**Atomic service API**: This API can be used in atomic services since API version 12. 793 794**System capability**: SystemCapability.ArkUI.ArkUI.Full 795 796**Parameters** 797 798| Name| Type | Mandatory| Description | 799| ------ | ------ | ---- | ------------------------------------------------------------------------ | 800| param | Object | No | Parameter used to reuse the BuilderNode. It is of the same type as the parameter passed to the [build](#build) API.| 801 802### recycle<sup>12+</sup> 803 804recycle(): void 805 806- Triggers recycling of custom components under this BuilderNode. Component recycling is part of the component reuse mechanism. For details, see [\@Reusable Decorator: Reusing Components](../../ui/state-management/arkts-reusable.md). 807- The BuilderNode completes the reuse event transfer between internal and external custom components through **reuse** and **recycle**. For specific usage scenarios, see [Implementing Node Reuse with the BuilderNode reuse and recycle APIs](../../ui/arkts-user-defined-arktsNode-builderNode.md#implementing-node-reuse-with-the-buildernode-reuse-and-recycle-apis). 808 809**Atomic service API**: This API can be used in atomic services since API version 12. 810 811**System capability**: SystemCapability.ArkUI.ArkUI.Full 812 813```ts 814import { FrameNode, NodeController, BuilderNode, UIContext } from "@kit.ArkUI"; 815 816const TEST_TAG: string = "Reuse+Recycle"; 817 818class MyDataSource { 819 private dataArray: string[] = []; 820 private listener: DataChangeListener | null = null; 821 822 public totalCount(): number { 823 return this.dataArray.length; 824 } 825 826 public getData(index: number) { 827 return this.dataArray[index]; 828 } 829 830 public pushData(data: string) { 831 this.dataArray.push(data); 832 } 833 834 public reloadListener(): void { 835 this.listener?.onDataReloaded(); 836 } 837 838 public registerDataChangeListener(listener: DataChangeListener): void { 839 this.listener = listener; 840 } 841 842 public unregisterDataChangeListener(): void { 843 this.listener = null; 844 } 845} 846 847class Params { 848 item: string = ''; 849 850 constructor(item: string) { 851 this.item = item; 852 } 853} 854 855@Builder 856function buildNode(param: Params = new Params("hello")) { 857 Row() { 858 Text(`C${param.item} -- `) 859 ReusableChildComponent2({ item: param.item }) // This custom component cannot be correctly reused in the BuilderNode. 860 } 861} 862 863class MyNodeController extends NodeController { 864 public builderNode: BuilderNode<[Params]> | null = null; 865 public item: string = ""; 866 867 makeNode(uiContext: UIContext): FrameNode | null { 868 if (this.builderNode == null) { 869 this.builderNode = new BuilderNode(uiContext, { selfIdealSize: { width: 300, height: 200 } }); 870 this.builderNode.build(wrapBuilder<[Params]>(buildNode), new Params(this.item)); 871 } 872 return this.builderNode.getFrameNode(); 873 } 874} 875 876// The custom component that is reused and recycled will have its state variables updated, and the state variables of the nested custom component ReusableChildComponent3 will also be updated. However, the BuilderNode will block this propagation process. 877@Reusable 878@Component 879struct ReusableChildComponent { 880 @Prop item: string = ''; 881 @Prop switch: string = ''; 882 private controller: MyNodeController = new MyNodeController(); 883 884 aboutToAppear() { 885 this.controller.item = this.item; 886 } 887 888 aboutToRecycle(): void { 889 console.info(`${TEST_TAG} ReusableChildComponent aboutToRecycle ${this.item}`); 890 891 // When the switch is open, pass the recycle event to the nested custom component, such as ReusableChildComponent2, through the BuilderNode's recycle API to complete recycling. 892 if (this.switch === 'open') { 893 this.controller?.builderNode?.recycle(); 894 } 895 } 896 897 aboutToReuse(params: object): void { 898 console.info(`${TEST_TAG} ReusableChildComponent aboutToReuse ${JSON.stringify(params)}`); 899 900 // When the switch is open, pass the reuse event to the nested custom component, such as ReusableChildComponent2, through the BuilderNode's reuse API to complete reuse. 901 if (this.switch === 'open') { 902 this.controller?.builderNode?.reuse(params); 903 } 904 } 905 906 build() { 907 Row() { 908 Text(`A${this.item}--`) 909 ReusableChildComponent3({ item: this.item }) 910 NodeContainer(this.controller); 911 } 912 } 913} 914 915@Component 916struct ReusableChildComponent2 { 917 @Prop item: string = "false"; 918 919 aboutToReuse(params: Record<string, object>) { 920 console.info(`${TEST_TAG} ReusableChildComponent2 aboutToReuse ${JSON.stringify(params)}`); 921 } 922 923 aboutToRecycle(): void { 924 console.info(`${TEST_TAG} ReusableChildComponent2 aboutToRecycle ${this.item}`); 925 } 926 927 build() { 928 Row() { 929 Text(`D${this.item}`) 930 .fontSize(20) 931 .backgroundColor(Color.Yellow) 932 .margin({ left: 10 }) 933 }.margin({ left: 10, right: 10 }) 934 } 935} 936 937@Component 938struct ReusableChildComponent3 { 939 @Prop item: string = "false"; 940 941 aboutToReuse(params: Record<string, object>) { 942 console.info(`${TEST_TAG} ReusableChildComponent3 aboutToReuse ${JSON.stringify(params)}`); 943 } 944 945 aboutToRecycle(): void { 946 console.info(`${TEST_TAG} ReusableChildComponent3 aboutToRecycle ${this.item}`); 947 } 948 949 build() { 950 Row() { 951 Text(`B${this.item}`) 952 .fontSize(20) 953 .backgroundColor(Color.Yellow) 954 .margin({ left: 10 }) 955 }.margin({ left: 10, right: 10 }) 956 } 957} 958 959 960@Entry 961@Component 962struct Index { 963 @State data: MyDataSource = new MyDataSource(); 964 965 aboutToAppear() { 966 for (let i = 0; i < 100; i++) { 967 this.data.pushData(i.toString()); 968 } 969 } 970 971 build() { 972 Column() { 973 List({ space: 3 }) { 974 LazyForEach(this.data, (item: string) => { 975 ListItem() { 976 ReusableChildComponent({ 977 item: item, 978 switch: 'open' // Changing open to close can be used to observe the behavior of custom components inside the BuilderNode when reuse and recycle events are not passed through the BuilderNode's reuse and recycle APIs. 979 }) 980 } 981 }, (item: string) => item) 982 } 983 .width('100%') 984 .height('100%') 985 } 986 } 987} 988``` 989 990### updateConfiguration<sup>12+</sup> 991 992updateConfiguration(): void 993 994Updates the configuration of the entire node by passing in a [system environment change](../apis-ability-kit/js-apis-app-ability-configuration.md) event. 995 996**Atomic service API**: This API can be used in atomic services since API version 12. 997 998**System capability**: SystemCapability.ArkUI.ArkUI.Full 999 1000> **NOTE** 1001> 1002> 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. 1003 1004**Example** 1005```ts 1006import { NodeController, BuilderNode, FrameNode, UIContext, FrameCallback } from "@kit.ArkUI"; 1007import { AbilityConstant, Configuration, ConfigurationConstant, EnvironmentCallback } from '@kit.AbilityKit'; 1008 1009class Params { 1010 text: string = ""; 1011 1012 constructor(text: string) { 1013 this.text = text; 1014 } 1015} 1016 1017// Custom component 1018@Component 1019struct TextBuilder { 1020 // The @Prop decorated attribute is the attribute to be updated in the custom component. It is a basic attribute. 1021 @Prop message: string = "TextBuilder"; 1022 1023 build() { 1024 Row() { 1025 Column() { 1026 Text(this.message) 1027 .fontSize(50) 1028 .fontWeight(FontWeight.Bold) 1029 .margin({ bottom: 36 }) 1030 } 1031 } 1032 } 1033} 1034 1035@Builder 1036function buildText(params: Params) { 1037 Column() { 1038 Text(params.text) 1039 .fontSize(50) 1040 .fontWeight(FontWeight.Bold) 1041 .margin({ bottom: 36 }) 1042 TextBuilder({ message: params.text }) // Custom component 1043 }.backgroundColor($r('sys.color.ohos_id_color_background')) 1044} 1045 1046class TextNodeController extends NodeController { 1047 private textNode: BuilderNode<[Params]> | null = null; 1048 private message: string = ""; 1049 1050 constructor(message: string) { 1051 super(); 1052 this.message = message; 1053 } 1054 1055 makeNode(context: UIContext): FrameNode | null { 1056 return this.textNode?.getFrameNode() ? this.textNode?.getFrameNode() : null; 1057 } 1058 1059 createNode(context: UIContext) { 1060 this.textNode = new BuilderNode(context); 1061 this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message)); 1062 builderNodeMap.push(this.textNode); 1063 } 1064 1065 deleteNode() { 1066 let node = builderNodeMap.pop(); 1067 node?.dispose(); 1068 } 1069 1070 update(message: string) { 1071 if (this.textNode !== null) { 1072 // Call update to perform an update. 1073 this.textNode.update(new Params(message)); 1074 } 1075 } 1076} 1077 1078// Record the created custom node object. 1079const builderNodeMap: Array<BuilderNode<[Params]>> = new Array(); 1080 1081class MyFrameCallback extends FrameCallback { 1082 onFrame() { 1083 updateColorMode(); 1084 } 1085} 1086 1087function updateColorMode() { 1088 builderNodeMap.forEach((value, index) => { 1089 // Notify the BuilderNode of the environment changes to trigger switching between light and dark modes. 1090 value.updateConfiguration(); 1091 }) 1092} 1093 1094@Entry 1095@Component 1096struct Index { 1097 @State message: string = "hello"; 1098 private textNodeController: TextNodeController = new TextNodeController(this.message); 1099 private count = 0; 1100 1101 aboutToAppear(): void { 1102 let environmentCallback: EnvironmentCallback = { 1103 onMemoryLevel: (level: AbilityConstant.MemoryLevel): void => { 1104 console.info('onMemoryLevel'); 1105 }, 1106 onConfigurationUpdated: (config: Configuration): void => { 1107 console.info('onConfigurationUpdated ' + JSON.stringify(config)); 1108 this.getUIContext()?.postFrameCallback(new MyFrameCallback()); 1109 } 1110 }; 1111 // Register a callback. 1112 this.getUIContext().getHostContext()?.getApplicationContext().on('environment', environmentCallback); 1113 // Set the application color mode to follow the system settings. 1114 this.getUIContext() 1115 .getHostContext()?.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); 1116 // Create a custom node and add it to the map. 1117 this.textNodeController.createNode(this.getUIContext()); 1118 } 1119 1120 aboutToDisappear(): void { 1121 // Remove the reference to the custom node from the map and release the node. 1122 this.textNodeController.deleteNode(); 1123 } 1124 1125 build() { 1126 Row() { 1127 Column() { 1128 NodeContainer(this.textNodeController) 1129 .width('100%') 1130 .height(200) 1131 .backgroundColor('#FFF0F0F0') 1132 Button('Update') 1133 .onClick(() => { 1134 this.count += 1; 1135 const message = "Update " + this.count.toString(); 1136 this.textNodeController.update(message); 1137 }) 1138 Button('Switch to Dark Mode') 1139 .onClick(() => { 1140 this.getUIContext() 1141 .getHostContext()?.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_DARK); 1142 }) 1143 Button('Switch to Light Mode') 1144 .onClick(() => { 1145 this.getUIContext() 1146 .getHostContext()?.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT); 1147 }) 1148 } 1149 .width('100%') 1150 .height('100%') 1151 } 1152 .height('100%') 1153 } 1154} 1155``` 1156 1157### isDisposed<sup>20+</sup> 1158 1159isDisposed(): boolean 1160 1161Checks whether this BuilderNode object has released its reference to its backend entity node. All frontend nodes are bound to corresponding backend entity nodes. After **dispose()** is called, subsequent calls may cause crashes or return default values. This API facilitates validation of node validity prior to operations, thereby mitigating risks in scenarios where calls after disposal are required. 1162 1163**Atomic service API**: This API can be used in atomic services since API version 20. 1164 1165**System capability**: SystemCapability.ArkUI.ArkUI.Full 1166 1167**Return value** 1168 1169| Type | Description | 1170| ------- | ------------------ | 1171| boolean | Whether the reference to the backend node is released. The value **true** means that the reference to backend node is released, and **false** means the opposite. 1172 1173### postInputEvent<sup>20+</sup> 1174 1175postInputEvent(event: InputEventType): boolean 1176 1177Dispatches the specified input event to the target node. 1178 1179**offsetA** indicates the BuilderNode's offset relative to its parent component, **offsetB** the hit position's offset relative to the BuilderNode, **offsetC** the composite offset (offsetA + offsetB) passed to the window in **postInputEvent**. 1180 1181 1182 1183> **NOTE** 1184> 1185> The passed coordinates must be converted to the unit of px. The sample code below demonstrates how to perform such coordinate conversion. 1186> 1187> Mouse left-click events are automatically converted to touch events. Avoid binding both touch and mouse events at the outer layer, as this may cause coordinate offsets. This is because the **SourceType** remains unchanged during event conversion. For details, see [onTouch](arkui-ts/ts-universal-events-touch.md#ontouch). 1188> 1189> Injected [axis events](arkui-ts/ts-universal-events-axis.md#axisevent) cannot trigger the following due to missing axis data: [pinch gesture](arkui-ts/ts-basic-gestures-pinchgesture.md) and [rotate gesture](arkui-ts/ts-basic-gestures-rotationgesture.md). 1190> 1191> A forwarded event undergoes touch testing in the target component's subtree and triggers corresponding gestures. The original event also triggers gestures in the source component tree. There is no guaranteed outcome for gesture competition between these two types of gestures. 1192> 1193> For developer-constructed events, mandatory fields must be assigned values, such as the **touches** field for touch events and the **scrollStep** field for axis events Ensure the completeness of the event, for example, both **DOWN** and **UP** [TouchType](arkui-ts/ts-appendix-enums.md#touchtype) states must be included for a touch event to prevent undefined behavior. 1194> 1195> [Webview](../apis-arkweb/arkts-apis-webview.md) has already handled coordinate system transformation, so events can be dispatched delivered. 1196> 1197> The **postTouchEvent** API needs to provide the gesture coordinates relative to the local coordinates of the target component, and the **postInputEvent** API needs to provide the gesture coordinates relative to the window coordinates. 1198> 1199> Avoid forwarding a single event multiple times. <!--Del-->[UIExtensionComponent](arkui-ts/ts-container-ui-extension-component-sys.md) is not supported.<!--DelEnd--> 1200 1201**Atomic service API**: This API can be used in atomic services since API version 20. 1202 1203**System capability**: SystemCapability.ArkUI.ArkUI.Full 1204 1205**Parameters** 1206 1207| Name| Type | Mandatory| Description | 1208| ------ | ------------------------------------------------------------------------- | ---- | ---------- | 1209| event | [InputEventType](#inputeventtype20) | Yes | Input event to dispatch.| 1210 1211**Return value** 1212 1213| Type | Description | 1214| ------- | ------------------ | 1215| boolean | Whether the event is successfully dispatched. The value **true** means that the event is successfully dispatched, and **false** means the opposite.| 1216 1217### inheritFreezeOptions<sup>20+</sup> 1218 1219inheritFreezeOptions(enabled: boolean): void 1220 1221Sets whether this **BuilderNode** object inherits the freeze policy from its parent component's custom components. When inheritance is disabled (set to **false**), the object's freeze policy is set to **false**, which means its associated node remains unfrozen even in an inactive state. 1222 1223**Atomic service API**: This API can be used in atomic services since API version 20. 1224 1225**System capability**: SystemCapability.ArkUI.ArkUI.Full 1226 1227**Parameters** 1228 1229| Name| Type | Mandatory| Description | 1230| ------ | ------ | ---- | ------------------------------------------------------------------------ | 1231| enabled | boolean | Yes | Whether the current **BuilderNode** object inherits the freeze policy from its parent component's custom components. The value **true** means to inherit the freeze policy from parent component's custom components, and **false** means the opposite.| 1232 1233## Example 1234 1235### Example 1: Handling Mouse Events 1236 1237This example demonstrates the end-to-end process for intercepting mouse events in a custom component and performing coordinate conversion. The component: 1. reads local X and Y coordinates through the **onMouse** callback; 2. combines the result with the offset obtained from **FrameNode.getPositionToParent()**; 3. converts relative coordinates to pixel coordinates using **vp2px**; 4. updates the **windowX**, **windowY**, **displayX**, and **displayY** values in **MouseEvent**; 5. dispatches the transformed event to child nodes through **rootNode.postInputEvent(event)**. 1238 1239```ts 1240import { NodeController, BuilderNode, FrameNode, UIContext, InputEventType } from '@kit.ArkUI'; 1241 1242class Params { 1243 text: string = "this is a text" 1244 uiContext: UIContext | null = null 1245} 1246 1247@Builder 1248function ButtonBuilder(params: Params) { 1249 Column() { 1250 Button(params.text) 1251 .borderWidth(2) 1252 .align(Alignment.Center) 1253 .backgroundColor(Color.Orange) 1254 .fontSize(20) 1255 .width("45%") 1256 .height("30%") 1257 .offset({ x: 100, y: 100 }) 1258 .onMouse(() => { 1259 console.info('onMouse') 1260 }) 1261 .onTouch(() => { 1262 console.info('onTouch') 1263 }) 1264 } 1265 .width(500) 1266 .height(300) 1267 .backgroundColor(Color.Gray) 1268} 1269 1270class MyNodeController extends NodeController { 1271 private rootNode: BuilderNode<[Params]> | null = null; 1272 private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(ButtonBuilder); 1273 1274 makeNode(uiContext: UIContext): FrameNode | null { 1275 this.rootNode = new BuilderNode(uiContext); 1276 this.rootNode.build(this.wrapBuilder, { text: "This is a string", uiContext }) 1277 return this.rootNode.getFrameNode(); 1278 } 1279 1280 postMouseEvent(event: InputEventType, uiContext: UIContext): boolean { 1281 if (this.rootNode == null) { 1282 return false; 1283 } 1284 let node: FrameNode | null = this.rootNode.getFrameNode(); 1285 let offsetX: number | null | undefined = node?.getPositionToParent().x; 1286 let offsetY: number | null | undefined = node?.getPositionToParent().y; 1287 1288 let mouseEvent = event as MouseEvent; 1289 if (offsetX != null && offsetY != null && offsetX != undefined && offsetY != undefined) { 1290 mouseEvent.windowX = uiContext.vp2px(offsetX + mouseEvent.x) 1291 mouseEvent.windowY = uiContext.vp2px(offsetY + mouseEvent.y) 1292 mouseEvent.displayX = uiContext.vp2px(offsetX + mouseEvent.x) 1293 mouseEvent.displayY = uiContext.vp2px(offsetY + mouseEvent.y) 1294 mouseEvent.x = uiContext.vp2px(mouseEvent.x) 1295 mouseEvent.y = uiContext.vp2px(mouseEvent.y) 1296 } 1297 1298 let result = this.rootNode.postInputEvent(event); 1299 return result; 1300 } 1301 1302 postTouchEvent(event: InputEventType, uiContext: UIContext): boolean { 1303 if (this.rootNode == null) { 1304 return false; 1305 } 1306 let node: FrameNode | null = this.rootNode.getFrameNode(); 1307 let offsetX: number | null | undefined = node?.getPositionToParent().x; 1308 let offsetY: number | null | undefined = node?.getPositionToParent().y; 1309 1310 let touchevent = event as TouchEvent; 1311 let changedTouchLen = touchevent.changedTouches.length; 1312 for (let i = 0; i < changedTouchLen; i++) { 1313 if (offsetX != null && offsetY != null && offsetX != undefined && offsetY != undefined) { 1314 touchevent.changedTouches[i].windowX = uiContext.vp2px(offsetX + touchevent.changedTouches[i].x); 1315 touchevent.changedTouches[i].windowY = uiContext.vp2px(offsetY + touchevent.changedTouches[i].y); 1316 touchevent.changedTouches[i].displayX = uiContext.vp2px(offsetX + touchevent.changedTouches[i].x); 1317 touchevent.changedTouches[i].displayY = uiContext.vp2px(offsetY + touchevent.changedTouches[i].y); 1318 } 1319 } 1320 let touchesLen = touchevent.touches.length; 1321 for (let i = 0; i < touchesLen; i++) { 1322 if (offsetX != null && offsetY != null && offsetX != undefined && offsetY != undefined) { 1323 touchevent.touches[i].windowX = uiContext.vp2px(offsetX + touchevent.touches[i].x); 1324 touchevent.touches[i].windowY = uiContext.vp2px(offsetY + touchevent.touches[i].y); 1325 touchevent.touches[i].displayX = uiContext.vp2px(offsetX + touchevent.touches[i].x); 1326 touchevent.touches[i].displayY = uiContext.vp2px(offsetY + touchevent.touches[i].y); 1327 } 1328 } 1329 1330 let result = this.rootNode.postInputEvent(event); 1331 return result; 1332 } 1333} 1334 1335@Entry 1336@Component 1337struct MyComponent { 1338 private nodeController: MyNodeController = new MyNodeController(); 1339 1340 build() { 1341 Stack() { 1342 NodeContainer(this.nodeController) 1343 .height(300) 1344 .width(500) 1345 Column() 1346 .width(500) 1347 .height(300) 1348 .backgroundColor(Color.Transparent) 1349 .onMouse((event) => { 1350 if (event != undefined) { 1351 this.nodeController.postMouseEvent(event, this.getUIContext()); 1352 } 1353 }) 1354 .onTouch((event) => { 1355 if (event != undefined) { 1356 this.nodeController.postTouchEvent(event, this.getUIContext()); 1357 } 1358 }) 1359 }.offset({ top: 100 }) 1360 } 1361} 1362``` 1363 1364 1365 1366### Example 2: Handling Touch Events 1367 1368This example shows the end-to-end process for intercepting touch events in a custom component and transforming touch point coordinates. The implementation: 1. iterates through **changedTouches** and **touches** arrays in the **onTouch** callback; 2. for each touch point, adds the component offset to the X and Y coordinates and converts the result to pixels using **vp2px**; 3. updates the **windowX**, **windowY**, **displayX**, and **displayY** values of each touch point; 4. dispatches the processed touch event to child nodes using **rootNode.postInputEvent(event)**. 1369 1370```ts 1371import { NodeController, BuilderNode, FrameNode, UIContext, PromptAction, InputEventType } from '@kit.ArkUI'; 1372 1373class Params { 1374 text: string = "this is a text" 1375 uiContext: UIContext | null = null 1376} 1377@Builder 1378function ButtonBuilder(params: Params) { 1379 Column() { 1380 Button(params.text) 1381 .borderWidth(2) 1382 .align(Alignment.Center) 1383 .backgroundColor(Color.Orange) 1384 .fontSize(20) 1385 .width("45%") 1386 .height("30%") 1387 .offset({x: 100, y: 100}) 1388 .onTouch((event) => { 1389 let promptAction: PromptAction = params.uiContext!.getPromptAction(); 1390 promptAction.showToast({ 1391 message: 'onTouch', 1392 duration: 3000 1393 }); 1394 console.info('onTouch') 1395 }) 1396 } 1397 .width(500) 1398 .height(300) 1399 .backgroundColor(Color.Gray) 1400} 1401class MyNodeController extends NodeController { 1402 private rootNode: BuilderNode<[Params]> | null = null; 1403 private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(ButtonBuilder); 1404 makeNode(uiContext: UIContext): FrameNode | null { 1405 this.rootNode = new BuilderNode(uiContext); 1406 this.rootNode.build(this.wrapBuilder, { text: "This is a string", uiContext }) 1407 return this.rootNode.getFrameNode(); 1408 } 1409 1410 postInputEvent(event: InputEventType, uiContext: UIContext): boolean { 1411 if (this.rootNode == null) { 1412 return false; 1413 } 1414 let node: FrameNode | null = this.rootNode.getFrameNode(); 1415 let offsetX: number | null | undefined = node?.getPositionToParent().x; 1416 let offsetY: number | null | undefined = node?.getPositionToParent().y; 1417 1418 // Forward only original touch events, not mouse-simulated touch events. 1419 if (event.source == SourceType.TouchScreen) { 1420 let touchevent = event as TouchEvent; 1421 let changedTouchLen = touchevent.changedTouches.length; 1422 for (let i = 0; i < changedTouchLen; i++) { 1423 if (offsetX != null && offsetY != null && offsetX != undefined && offsetY != undefined) { 1424 touchevent.changedTouches[i].windowX = uiContext.vp2px(offsetX + touchevent.changedTouches[i].x); 1425 touchevent.changedTouches[i].windowY = uiContext.vp2px(offsetY + touchevent.changedTouches[i].y); 1426 touchevent.changedTouches[i].displayX = uiContext.vp2px(offsetX + touchevent.changedTouches[i].x); 1427 touchevent.changedTouches[i].displayY = uiContext.vp2px(offsetY + touchevent.changedTouches[i].y); 1428 } 1429 } 1430 let touchesLen = touchevent.touches.length; 1431 for (let i = 0; i < touchesLen; i++) { 1432 if (offsetX != null && offsetY != null && offsetX != undefined && offsetY != undefined) { 1433 touchevent.touches[i].windowX = uiContext.vp2px(offsetX + touchevent.touches[i].x); 1434 touchevent.touches[i].windowY = uiContext.vp2px(offsetY + touchevent.touches[i].y); 1435 touchevent.touches[i].displayX = uiContext.vp2px(offsetX + touchevent.touches[i].x); 1436 touchevent.touches[i].displayY = uiContext.vp2px(offsetY + touchevent.touches[i].y); 1437 } 1438 } 1439 } 1440 1441 let result = this.rootNode.postInputEvent(event); 1442 return result; 1443 } 1444} 1445@Entry 1446@Component 1447struct MyComponent { 1448 private nodeController: MyNodeController = new MyNodeController(); 1449 build() { 1450 Stack() { 1451 NodeContainer(this.nodeController) 1452 .height(300) 1453 .width(500) 1454 Column() 1455 .width(500) 1456 .height(300) 1457 .backgroundColor(Color.Transparent) 1458 .onTouch((event) => { 1459 if (event != undefined) { 1460 this.nodeController.postInputEvent(event, this.getUIContext()); 1461 } 1462 }) 1463 }.offset({top: 100}) 1464 } 1465} 1466``` 1467 1468 1469 1470### Example 3: Handling Axis Events 1471 1472This example demonstrates the end-to-end process for intercepting wheel or trackpad axis events in a custom component and performing coordinate conversion. The implementation: 1. obtains relative X and Y coordinates from the **onAxisEvent** callback; 2. adds the component offset and converts the result to pixels using **vp2px**; 3. updates the **windowX**, **windowY**, **displayX**, and **displayY** values in **AxisEvent**; 4. dispatches the transformed axis event to child nodes using **rootNode.postInputEvent(event)**. 1473 1474```ts 1475import { NodeController, BuilderNode, FrameNode, UIContext, PromptAction, InputEventType } from '@kit.ArkUI'; 1476 1477class Params { 1478 text: string = "this is a text" 1479 uiContext: UIContext | null = null 1480} 1481@Builder 1482function ButtonBuilder(params: Params) { 1483 Column() { 1484 Button(params.text) 1485 .borderWidth(2) 1486 .align(Alignment.Center) 1487 .backgroundColor(Color.Orange) 1488 .fontSize(20) 1489 .width("45%") 1490 .height("30%") 1491 .offset({x: 100, y: 100}) 1492 .onAxisEvent((event) => { 1493 let promptAction: PromptAction = params.uiContext!.getPromptAction(); 1494 promptAction.showToast({ 1495 message: 'onAxisEvent', 1496 duration: 3000 1497 }); 1498 console.info('onAxisEvent') 1499 }) 1500 } 1501 .width(500) 1502 .height(300) 1503 .backgroundColor(Color.Gray) 1504} 1505class MyNodeController extends NodeController { 1506 private rootNode: BuilderNode<[Params]> | null = null; 1507 private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(ButtonBuilder); 1508 makeNode(uiContext: UIContext): FrameNode | null { 1509 this.rootNode = new BuilderNode(uiContext); 1510 this.rootNode.build(this.wrapBuilder, { text: "This is a string", uiContext }) 1511 return this.rootNode.getFrameNode(); 1512 } 1513 1514 postInputEvent(event: InputEventType, uiContext: UIContext): boolean { 1515 if (this.rootNode == null) { 1516 return false; 1517 } 1518 let node: FrameNode | null = this.rootNode.getFrameNode(); 1519 let offsetX: number | null | undefined = node?.getPositionToParent().x; 1520 let offsetY: number | null | undefined = node?.getPositionToParent().y; 1521 1522 let axiseEvent = event as AxisEvent; 1523 if (offsetX != null && offsetY != null && offsetX != undefined && offsetY != undefined) { 1524 axiseEvent.windowX = uiContext.vp2px(offsetX + axiseEvent.x) 1525 axiseEvent.windowY = uiContext.vp2px(offsetY + axiseEvent.y) 1526 axiseEvent.displayX = uiContext.vp2px(offsetX + axiseEvent.x) 1527 axiseEvent.displayY = uiContext.vp2px(offsetY + axiseEvent.y) 1528 axiseEvent.x = uiContext.vp2px(axiseEvent.x) 1529 axiseEvent.y = uiContext.vp2px(axiseEvent.y) 1530 } 1531 1532 let result = this.rootNode.postInputEvent(event); 1533 return result; 1534 } 1535} 1536@Entry 1537@Component 1538struct MyComponent { 1539 private nodeController: MyNodeController = new MyNodeController(); 1540 build() { 1541 Stack() { 1542 NodeContainer(this.nodeController) 1543 .height(300) 1544 .width(500) 1545 Column() 1546 .width(500) 1547 .height(300) 1548 .backgroundColor(Color.Transparent) 1549 .onAxisEvent((event) => { 1550 if (event != undefined) { 1551 this.nodeController.postInputEvent(event, this.getUIContext()); 1552 } 1553 }) 1554 }.offset({top: 100}) 1555 } 1556} 1557``` 1558 1559 1560 1561### Example 4: Passing a BuilderNode Shared localStorage Instance 1562This example demonstrates how to pass an external **localStorage** instance to a BuilderNode through the **build** method, enabling all custom components mounted under the BuilderNode to share the same **localStorage** instance. 1563```ts 1564import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI'; 1565 1566class Params { 1567 text: string = "" 1568 constructor(text: string) { 1569 this.text = text; 1570 } 1571} 1572 1573let globalBuilderNode: BuilderNode<[Params]> | null = null; 1574 1575@Builder 1576function buildText(params: Params) { 1577 Column() { 1578 Text('BuildNodeContentArea') 1579 .fontSize(25) 1580 CustomComp() 1581 } 1582} 1583 1584class TextNodeController extends NodeController { 1585 private rootNode: FrameNode | null = null; 1586 makeNode(context: UIContext): FrameNode | null { 1587 this.rootNode = new FrameNode(context); 1588 if (globalBuilderNode === null) { 1589 globalBuilderNode = new BuilderNode(context); 1590 globalBuilderNode.build(wrapBuilder<[Params]>(buildText), new Params('builder node text'), { localStorage: localStorage1 }) 1591 } 1592 this.rootNode.appendChild(globalBuilderNode.getFrameNode()); 1593 return this.rootNode; 1594 } 1595} 1596 1597let localStorage1: LocalStorage = new LocalStorage(); 1598localStorage1.setOrCreate('PropA', 'PropA'); 1599 1600@Entry(localStorage1) 1601@Component 1602struct Index { 1603 private controller: TextNodeController = new TextNodeController(); 1604 @LocalStorageLink('PropA') PropA: string = 'Hello World'; 1605 build() { 1606 Row() { 1607 Column() { 1608 Text(this.PropA) 1609 NodeContainer(this.controller) 1610 Button('changeLocalstorage').onClick(()=>{ 1611 localStorage1.set('PropA','AfterChange') 1612 }) 1613 } 1614 } 1615 } 1616} 1617@Component 1618struct CustomComp { 1619 @LocalStorageLink('PropA') PropA: string = 'Hello World'; 1620 build() { 1621 Row() { 1622 Column() { 1623 Text(this.PropA) 1624 } 1625 } 1626 } 1627} 1628``` 1629### Example 5: Checking BuilderNode Reference Status 1630 1631This example shows how to verify a BuilderNode's state using the **isDisposed** API. This API returns **true** before node release and **false** after node release. 1632 1633```ts 1634import { RenderNode, FrameNode, NodeController, BuilderNode } from "@kit.ArkUI"; 1635 1636@Component 1637struct TestComponent { 1638 build() { 1639 Column() { 1640 Text('This is a BuilderNode.') 1641 .fontSize(25) 1642 .fontWeight(FontWeight.Bold) 1643 } 1644 .width('100%') 1645 .height(30) 1646 .backgroundColor(Color.Gray) 1647 } 1648 1649 aboutToAppear() { 1650 console.info('aboutToAppear'); 1651 } 1652 1653 aboutToDisappear() { 1654 console.info('aboutToDisappear'); 1655 } 1656} 1657 1658@Builder 1659function buildComponent() { 1660 TestComponent() 1661} 1662 1663class MyNodeController extends NodeController { 1664 private rootNode: FrameNode | null = null; 1665 private builderNode: BuilderNode<[]> | null = null; 1666 1667 makeNode(uiContext: UIContext): FrameNode | null { 1668 this.rootNode = new FrameNode(uiContext); 1669 this.builderNode = new BuilderNode(uiContext, { selfIdealSize: { width: 200, height: 100 } }); 1670 this.builderNode.build(new WrappedBuilder(buildComponent)); 1671 1672 const rootRenderNode = this.rootNode!.getRenderNode(); 1673 if (rootRenderNode !== null) { 1674 rootRenderNode.size = { width: 300, height: 300 }; 1675 rootRenderNode.backgroundColor = 0xffd5d5d5; 1676 rootRenderNode.appendChild(this.builderNode!.getFrameNode()!.getRenderNode()); 1677 } 1678 1679 return this.rootNode; 1680 } 1681 1682 dispose() { 1683 if (this.builderNode !== null) { 1684 this.builderNode.dispose(); 1685 } 1686 } 1687 1688 isDisposed() : string{ 1689 if (this.builderNode !== null) { 1690 if (this.builderNode.isDisposed()) { 1691 return 'builderNode isDisposed is true'; 1692 } 1693 else { 1694 return 'builderNode isDisposed is false'; 1695 } 1696 } 1697 return 'builderNode is null'; 1698 } 1699 1700 removeBuilderNode() { 1701 const rootRenderNode = this.rootNode!.getRenderNode(); 1702 if (rootRenderNode !== null && this.builderNode !== null && this.builderNode.getFrameNode() !== null) { 1703 rootRenderNode.removeChild(this.builderNode!.getFrameNode()!.getRenderNode()); 1704 } 1705 } 1706} 1707 1708@Entry 1709@Component 1710struct Index { 1711 @State text: string = '' 1712 private myNodeController: MyNodeController = new MyNodeController(); 1713 1714 build() { 1715 Column({ space: 4 }) { 1716 NodeContainer(this.myNodeController) 1717 Button('BuilderNode dispose') 1718 .onClick(() => { 1719 this.myNodeController.removeBuilderNode(); 1720 this.myNodeController.dispose(); 1721 this.text = ''; 1722 }) 1723 .width(200) 1724 .height(50) 1725 Button('BuilderNode isDisposed') 1726 .onClick(() => { 1727 this.text = this.myNodeController.isDisposed(); 1728 }) 1729 .width(200) 1730 .height(50) 1731 Text(this.text) 1732 .fontSize(25) 1733 } 1734 .width('100%') 1735 .height('100%') 1736 } 1737} 1738``` 1739 1740 1741 1742### Example 6: Configuring BuilderNode Freeze Inheritance 1743 1744This example illustrates how to configure the BuilderNode to inherit the freeze policy from its parent component, resulting in the following behavior: It automatically freezes when in inactive state and thaws and updates cached data when in active state. 1745 1746```ts 1747 1748import { BuilderNode, FrameNode, NodeController } from '@kit.ArkUI'; 1749 1750class Params { 1751 count: number = 0; 1752 1753 constructor(count: number) { 1754 this.count = count; 1755 } 1756} 1757 1758@Builder // Builder component 1759function buildText(params: Params) { 1760 1761 Column() { 1762 TextBuilder({ message: params.count }) 1763 } 1764} 1765 1766class TextNodeController extends NodeController { 1767 private rootNode: FrameNode | null = null; 1768 private textNode: BuilderNode<[Params]> | null = null; 1769 private count: number = 0; 1770 1771 makeNode(context: UIContext): FrameNode | null { 1772 this.rootNode = new FrameNode(context); 1773 this.textNode = new BuilderNode(context, { selfIdealSize: { width: 150, height: 150 } }); 1774 this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.count)); // Create a BuilderNode node. 1775 this.textNode.inheritFreezeOptions(true); // Configure the BuilderNode to inherit the freeze policy from its parent component. 1776 if (this.rootNode !== null) { 1777 this.rootNode.appendChild(this.textNode.getFrameNode()); // Mount the BuilderNode to the component tree. 1778 } 1779 return this.rootNode; 1780 } 1781 1782 update(): void { 1783 if (this.textNode !== null) { 1784 this.count += 1; 1785 this.textNode.update(new Params(this.count)); // Update the BuilderNode data, which triggers logs. 1786 } 1787 1788 } 1789} 1790 1791const textNodeController: TextNodeController = new TextNodeController(); 1792 1793@Entry 1794@Component 1795struct MyNavigationTestStack { 1796 @Provide('pageInfo') pageInfo: NavPathStack = new NavPathStack(); 1797 @State message: number = 0; 1798 @State logNumber: number = 0; 1799 1800 @Builder 1801 PageMap(name: string) { 1802 if (name === 'pageOne') { 1803 pageOneStack({ message: this.message, logNumber: this.logNumber }) 1804 } else if (name === 'pageTwo') { 1805 pageTwoStack({ message: this.message, logNumber: this.logNumber }) 1806 } 1807 } 1808 1809 build() { 1810 Column() { 1811 Button('update builderNode') // Clicking the button updates builderNode. 1812 .onClick(() => { 1813 textNodeController.update(); 1814 }) 1815 Navigation(this.pageInfo) { 1816 Column() { 1817 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 1818 .width('80%') 1819 .height(40) 1820 .margin(20) 1821 .onClick(() => { 1822 this.pageInfo.pushPath({ name: 'pageOne' }); // Push the navigation destination page specified by name to the navigation stack. 1823 }) 1824 } 1825 }.title('NavIndex') 1826 .navDestination(this.PageMap) 1827 .mode(NavigationMode.Stack) 1828 } 1829 } 1830} 1831 1832@Component 1833struct pageOneStack { // Page 1 1834 @Consume('pageInfo') pageInfo: NavPathStack; 1835 @State index: number = 1; 1836 @Link message: number; 1837 @Link logNumber: number; 1838 1839 build() { 1840 NavDestination() { 1841 Column() { 1842 NavigationContentMsgStack({ message: this.message, index: this.index, logNumber: this.logNumber }) 1843 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) // Navigate to page 2. 1844 .width('80%') 1845 .height(40) 1846 .margin(20) 1847 .onClick(() => { 1848 this.pageInfo.pushPathByName('pageTwo', null); 1849 }) 1850 Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) // Return to the home page. 1851 .width('80%') 1852 .height(40) 1853 .margin(20) 1854 .onClick(() => { 1855 this.pageInfo.pop(); 1856 }) 1857 }.width('100%').height('100%') 1858 }.title('pageOne') 1859 .onBackPressed(() => { 1860 this.pageInfo.pop(); 1861 return true; 1862 }) 1863 } 1864} 1865 1866@Component 1867struct pageTwoStack { // Page 2 1868 @Consume('pageInfo') pageInfo: NavPathStack; 1869 @State index: number = 2; 1870 @Link message: number; 1871 @Link logNumber: number; 1872 1873 build() { 1874 NavDestination() { 1875 Column() { 1876 NavigationContentMsgStack({ message: this.message, index: this.index, logNumber: this.logNumber }) 1877 Text('BuilderNode is frozen') 1878 .fontWeight(FontWeight.Bold) 1879 .margin({ top: 48, bottom: 48 }) 1880 Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) // Return to page 1. 1881 .width('80%') 1882 .height(40) 1883 .margin(20) 1884 .onClick(() => { 1885 this.pageInfo.pop(); 1886 }) 1887 }.width('100%').height('100%') 1888 }.title('pageTwo') 1889 .onBackPressed(() => { 1890 this.pageInfo.pop(); 1891 return true; 1892 }) 1893 } 1894} 1895 1896@Component({ freezeWhenInactive: true }) // Set the freeze policy to freeze when inactive. 1897struct NavigationContentMsgStack { 1898 @Link message: number; 1899 @Link index: number; 1900 @Link logNumber: number; 1901 1902 build() { 1903 Column() { 1904 if (this.index === 1) { 1905 NodeContainer(textNodeController) 1906 } 1907 } 1908 } 1909} 1910 1911@Component({ freezeWhenInactive: true }) // Set the freeze policy to freeze when inactive. 1912struct TextBuilder { 1913 @Prop @Watch("info") message: number = 0; 1914 1915 info() { 1916 console.info(`freeze-test TextBuilder message callback ${this.message}`); // Print logs based on the message content change to determine whether the freeze occurs. 1917 } 1918 1919 build() { 1920 Row() { 1921 Column() { 1922 Text(`Update count: ${this.message}`) 1923 .fontWeight(FontWeight.Bold) 1924 .margin({ top: 48, bottom: 48 }) 1925 } 1926 } 1927 } 1928} 1929``` 1930 1931 1932 1933### Example 7: Configuring the BuilderNode for Cross-Boundary @Provide-@Consume Communication 1934 1935This example demonstrates how to enable data flow between @Provide in host components and @Consume in BuilderNode's internal components by setting **enableProvideConsumeCrossing** to **true** in **BuildOptions**. 1936 1937```ts 1938import { BuilderNode, NodeContent } from '@kit.ArkUI'; 1939 1940@Component 1941struct ConsumeChild { 1942 @Consume @Watch("ChangeData") message: string = "" 1943 1944 ChangeData() { 1945 console.info(`ChangeData ${this.message}`); 1946 } 1947 1948 build() { 1949 Column() { 1950 Text(this.message) 1951 .fontWeight(FontWeight.Bold) 1952 .fontSize(20) 1953 Button("Click to change message to append C") 1954 .fontWeight(FontWeight.Bold) 1955 .onClick(() => { 1956 // Modify the @Consume decorated variable. 1957 this.message = this.message + "C" 1958 }) 1959 } 1960 } 1961} 1962 1963@Builder 1964function CreateText(textMessage: string) { 1965 Column() { 1966 Text(textMessage) 1967 .fontWeight(FontWeight.Bold) 1968 .fontSize(20) 1969 ConsumeChild() 1970 } 1971} 1972 1973@Entry 1974@Component 1975struct Index { 1976 @Provide message: string = 'Hello World'; 1977 private content: NodeContent = new NodeContent(); 1978 private builderNode: BuilderNode<[string]> = new BuilderNode<[string]>(this.getUIContext()); 1979 1980 aboutToAppear(): void { 1981 // Enable data flow between @Provide in host components and @Consume in BuilderNode's internal components. 1982 this.builderNode.build(wrapBuilder(CreateText), "Test Consume", { enableProvideConsumeCrossing: true }) 1983 this.content.addFrameNode(this.builderNode.getFrameNode()) 1984 } 1985 1986 build() { 1987 Column() { 1988 Text(this.message) 1989 .fontWeight(FontWeight.Bold) 1990 .fontSize(20) 1991 Button("Click to change message to append I") 1992 .fontWeight(FontWeight.Bold) 1993 .onClick(() => { 1994 this.message = this.message + "I"; 1995 }) 1996 Column() { 1997 ContentSlot(this.content) 1998 } 1999 } 2000 .height('100%') 2001 .width('100%') 2002 } 2003} 2004``` 2005 2006 2007<!--no_check-->