• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# BuilderNode
2
3## Overview
4
5The [BuilderNode](../reference/apis-arkui/js-apis-arkui-builderNode.md) provides the capability to mount built-in components. It creates component trees with a stateless UI method, the [global custom builder function](../quick-start/arkts-builder.md#global-custom-builder-function), which is decorated by @Builder, and accesses the root [FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md) through [getFrameNode](../reference/apis-arkui/js-apis-arkui-builderNode.md#getframenode). This node can be directly returned by [NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md) and mounted under the [NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md) node. It also enables embedding declarative component structures within **FrameNode** and [RenderNode](../reference/apis-arkui/js-apis-arkui-renderNode.md) tree structures for mixed display capabilities. Additionally, the BuilderNode offers texture export functionality, with exported textures used for same-layer rendering in the [XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md).
6
7The BuilderNode enables the combination of ArkTS built-in component trees with custom nodes (such as FrameNodes and RenderNodes) for mixed display. It also allows built-in components to be embedded within third-party frameworks that use custom nodes.
8
9The BuilderNode offers the capability to pre-create built-in components, enabling their initialization at customized times and dynamic mounting as needed in operations. This is particularly useful for reducing the initialization time of declarative components such as [Web](../reference/apis-arkweb/ts-basic-components-web.md) and [XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md), which typically take longer to initialize.
10
11![en-us_image_builder-node](figures/builder-node.png)
12
13A BuilderNode can be used only as a leaf node. If an update is required, you are advised to use the update APIs provided by the BuilderNode, rather than making modifications directly to the RenderNode obtained from it.
14
15> **NOTE**
16>
17> - The BuilderNode only supports a single [global custom build function(../quick-start/arkts-builder.md#global-custom-builder-function) decorated by @Builder and wrapped by [wrapBuilder](../quick-start/arkts-wrapBuilder.md).
18>
19> - A newly created BuilderNode can only obtain a **FrameNode** object pointing to the root node through [getFrameNode](../reference/apis-arkui/js-apis-arkui-builderNode.md#getframenode) after [build](../reference/apis-arkui/js-apis-arkui-builderNode.md#build); otherwise, it returns **null**.
20>
21> - If the root node of the passed Builder is a syntactic node (such as **if/else** and **ForEach**), an additional FrameNode must be generated, which will be displayed as "BuilderProxyNode" in the node tree.
22>
23> - If BuilderNode mounts a node onto another FrameNode through **getFrameNode**, or mounts it as a child node onto a **NodeContainer**, the node uses the layout constraints of the parent component for layout.
24>
25> - If a BuilderNode's FrameNode mounts its node onto a RenderNode through [getRenderNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#getrendernode), its size defaults to **0** since its FrameNode is not yet part of the tree. To display it properly, you must explicitly specify the layout constraint size through [selfIdeaSize](../reference/apis-arkui/js-apis-arkui-builderNode.md#renderoptions) in the constructor.
26>
27> - Pre-creation with the BuilderNode does not reduce the creation time of components. For the **Web** component, resources must be loaded in the kernel during creation, and pre-creation cannot reduce this time. However, it enables the kernel to preload resources, which can reduce the loading time when the component is used.
28
29## Creating a BuilderNode Object
30
31When creating a **BuilderNode** object, which is a template class, you must specify a type that matches the type of the [WrappedBuilder](../quick-start/arkts-wrapBuilder.md) used in the **build** method later on. Mismatches can cause compilation warnings and failures.
32
33## Creating a Built-in Component Tree
34
35Use the **build** API of **BuilderNode** to create a built-in component tree. The tree is constructed based on the **WrappedBuilder** object passed in, and the root node of the component tree is retained.
36
37> **NOTE**
38>
39> Stateless UI methods using the global @Builder can have at most one root node.
40>
41> The @Builder within the **build** method accepts only one input parameter.
42>
43> In scenarios where @Builder is nested within another @Builder in the **build** method, ensure that the parameters of the nested @Builder match the input parameters provided to the **build** method.
44>
45> For scenarios where @Builder is nested within another @Builder, if the parameter types do not match, you must include the BuilderOptions](../reference/apis-arkui/js-apis-arkui-builderNode.md#buildoptions12) field as a parameter for the [build](../reference/apis-arkui/js-apis-arkui-builderNode.md#build12) method.
46
47Create offline nodes and built-in component trees, and use them in conjunction with FrameNodes.
48
49The root node of the BuilderNode is directly used as the return value of [makeNode](../reference/apis-arkui/js-apis-arkui-nodeController.md#makenode) of [NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md).
50
51```ts
52import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI'
53
54class Params {
55  text: string = ""
56
57  constructor(text: string) {
58    this.text = text;
59  }
60}
61
62@Builder
63function buildText(params: Params) {
64  Column() {
65    Text(params.text)
66      .fontSize(50)
67      .fontWeight(FontWeight.Bold)
68      .margin({ bottom: 36 })
69  }
70}
71
72class TextNodeController extends NodeController {
73  private textNode: BuilderNode<[Params]> | null = null;
74  private message: string = "DEFAULT";
75
76  constructor(message: string) {
77    super();
78    this.message = message;
79  }
80
81  makeNode(context: UIContext): FrameNode | null {
82    this.textNode = new BuilderNode(context);
83    this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message))
84    return this.textNode.getFrameNode();
85  }
86}
87
88@Entry
89@Component
90struct Index {
91  @State message: string = "hello"
92
93  build() {
94    Row() {
95      Column() {
96        NodeContainer(new TextNodeController(this.message))
97          .width('100%')
98          .height(100)
99          .backgroundColor('#FFF0F0F0')
100      }
101      .width('100%')
102      .height('100%')
103    }
104    .height('100%')
105  }
106}
107```
108
109When combining a BuilderNode with a RenderNode, note the following:
110
111If you mount the RenderNode from the BuilderNode under another RenderNode, you must explicitly specify [selfIdeaSize](../reference/apis-arkui/js-apis-arkui-builderNode.md#renderoptions) as the layout constraint for the BuilderNode. This approach to mounting nodes is not recommended.
112
113```ts
114import { NodeController, BuilderNode, FrameNode, UIContext, RenderNode } from "@kit.ArkUI"
115
116class Params {
117  text: string = ""
118
119  constructor(text: string) {
120    this.text = text;
121  }
122}
123
124@Builder
125function buildText(params: Params) {
126  Column() {
127    Text(params.text)
128      .fontSize(50)
129      .fontWeight(FontWeight.Bold)
130      .margin({ bottom: 36 })
131  }
132}
133
134class TextNodeController extends NodeController {
135  private rootNode: FrameNode | null = null;
136  private textNode: BuilderNode<[Params]> | null = null;
137  private message: string = "DEFAULT";
138
139  constructor(message: string) {
140    super();
141    this.message = message;
142  }
143
144  makeNode(context: UIContext): FrameNode | null {
145    this.rootNode = new FrameNode(context);
146    let renderNode = new RenderNode();
147    renderNode.clipToFrame = false;
148    this.textNode = new BuilderNode(context, { selfIdealSize: { width: 150, height: 150 } });
149    this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message));
150    const textRenderNode = this.textNode?.getFrameNode()?.getRenderNode();
151
152    const rootRenderNode = this.rootNode.getRenderNode();
153    if (rootRenderNode !== null) {
154      rootRenderNode.appendChild(renderNode);
155      renderNode.appendChild(textRenderNode);
156    }
157
158    return this.rootNode;
159  }
160}
161
162@Entry
163@Component
164struct Index {
165  @State message: string = "hello"
166
167  build() {
168    Row() {
169      Column() {
170        NodeContainer(new TextNodeController(this.message))
171          .width('100%')
172          .height(100)
173          .backgroundColor('#FFF0F0F0')
174      }
175      .width('100%')
176      .height('100%')
177    }
178    .height('100%')
179  }
180}
181```
182
183## Updating the Built-in Component Tree
184
185Create a built-in component tree using the **build** API of a **BuilderNode** object. The tree is constructed based on the **WrappedBuilder** object passed in, and the root node of the component tree is retained.
186
187Custom component updates follow the update mechanisms of [state management](../quick-start/arkts-state-management-overview.md). For custom components used directly in a **WrappedBuilder** object, their parent component is the **BuilderNode** object. Therefore, to update child components defined in the **WrappedBuilder** objects, you need to define the relevant state variables with the [@Prop](../quick-start/arkts-prop.md) or [@ObjectLink](../quick-start/arkts-observed-and-objectlink.md) decorator, in accordance with the specifications of state management and the needs of your application development.
188
189To update nodes within a BuilderNode:
190
191- Use the **update** API to update individual nodes within the BuilderNode.
192- Use the [updateConfiguration](../reference/apis-arkui/js-apis-arkui-builderNode.md#updateconfiguration12) API to trigger a full update of all nodes within the BuilderNode.
193
194
195
196```ts
197import { NodeController, BuilderNode, FrameNode, UIContext } from "@kit.ArkUI"
198
199class Params {
200  text: string = ""
201  constructor(text: string) {
202    this.text = text;
203  }
204}
205
206// Custom component
207@Component
208struct TextBuilder {
209  // The @Prop decorated attribute is the attribute to be updated in the custom component. It is a basic attribute.
210  @Prop message: string = "TextBuilder";
211
212  build() {
213    Row() {
214      Column() {
215        Text(this.message)
216          .fontSize(50)
217          .fontWeight(FontWeight.Bold)
218          .margin({ bottom: 36 })
219          .backgroundColor(Color.Gray)
220      }
221    }
222  }
223}
224
225@Builder
226function buildText(params: Params) {
227  Column() {
228    Text(params.text)
229      .fontSize(50)
230      .fontWeight(FontWeight.Bold)
231      .margin({ bottom: 36 })
232    TextBuilder({ message: params.text }) // Custom component
233  }
234}
235
236class TextNodeController extends NodeController {
237  private textNode: BuilderNode<[Params]> | null = null;
238  private message: string = "";
239
240  constructor(message: string) {
241    super()
242    this.message = message
243  }
244
245  makeNode(context: UIContext): FrameNode | null {
246    this.textNode = new BuilderNode(context);
247    this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message))
248    return this.textNode.getFrameNode();
249  }
250
251  update(message: string) {
252    if (this.textNode !== null) {
253      // Call update to perform an update.
254      this.textNode.update(new Params(message));
255    }
256  }
257}
258
259@Entry
260@Component
261struct Index {
262  @State message: string = "hello"
263  private textNodeController: TextNodeController = new TextNodeController(this.message);
264  private count = 0;
265
266  build() {
267    Row() {
268      Column() {
269        NodeContainer(this.textNodeController)
270          .width('100%')
271          .height(200)
272          .backgroundColor('#FFF0F0F0')
273        Button('Update')
274          .onClick(() => {
275            this.count += 1;
276            const message = "Update " + this.count.toString();
277            this.textNodeController.update(message);
278          })
279      }
280      .width('100%')
281      .height('100%')
282    }
283    .height('100%')
284  }
285}
286```
287
288## Canceling the Reference to the Entity Node
289
290A **BuilderNode** object is mapped to a backend entity node, and its memory release is usually contingent on the disposal of the frontend object. To directly release the backend node object, you can call the [dispose](../reference/apis-arkui/js-apis-arkui-builderNode.md#dispose12) API to break the reference to the entity node. Once this is done, the frontend **BuilderNode** object will no longer affect the lifecycle of the entity node.
291
292> **NOTE**
293>
294> 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.
295
296## Injecting a Touch Event
297
298Use the [postTouchEvent](../reference/apis-arkui/js-apis-arkui-builderNode.md#posttouchevent) API in the BuilderNode to inject a [touch event](../reference/apis-arkui/arkui-ts/ts-universal-events-touch.md) into the bound component for event simulation and forwarding.
299
300The following example forwards a touch event from one **Column** component to another in the BuilderNode, so that when the lower **Column** component is touched, the upper **Column** component also receives the same touch event. The API returns **true** if the button's event is successfully recognized.
301
302```ts
303import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI';
304
305class Params {
306  text: string = "this is a text"
307}
308
309@Builder
310function ButtonBuilder(params: Params) {
311  Column() {
312    Button(`button ` + params.text)
313      .borderWidth(2)
314      .backgroundColor(Color.Orange)
315      .width("100%")
316      .height("100%")
317      .gesture(
318        TapGesture()
319          .onAction((event: GestureEvent) => {
320            console.log("TapGesture");
321          })
322      )
323  }
324  .width(500)
325  .height(300)
326  .backgroundColor(Color.Gray)
327}
328
329class MyNodeController extends NodeController {
330  private rootNode: BuilderNode<[Params]> | null = null;
331  private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(ButtonBuilder);
332
333  makeNode(uiContext: UIContext): FrameNode | null {
334    this.rootNode = new BuilderNode(uiContext);
335    this.rootNode.build(this.wrapBuilder, { text: "this is a string" })
336    return this.rootNode.getFrameNode();
337  }
338
339  postTouchEvent(touchEvent: TouchEvent): void {
340    if (this.rootNode == null) {
341      return;
342    }
343    let result = this.rootNode.postTouchEvent(touchEvent);
344    console.log("result " + result);
345  }
346}
347
348@Entry
349@Component
350struct MyComponent {
351  private nodeController: MyNodeController = new MyNodeController();
352
353  build() {
354    Column() {
355      NodeContainer(this.nodeController)
356        .height(300)
357        .width(500)
358      Column()
359        .width(500)
360        .height(300)
361        .backgroundColor(Color.Pink)
362        .onTouch((event) => {
363          if (event != undefined) {
364            this.nodeController.postTouchEvent(event);
365          }
366        })
367    }
368  }
369}
370```
371<!--no_check-->