• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Custom Component Node (FrameNode)
2
3## Overview
4
5For third-party frameworks with custom frontend definitions, such as those in JSON, XML, or a DOM tree, a conversion from the specific DSL into ArkUI's declarative descriptions is necessary. The following figure shows the mapping between a frontend framework defined by JSON and the ArkUI declarative description.
6
7![en-us_image_frame-node01](figures/frame-node01.png)
8
9The aforementioned conversion process, which relies on additional data-driven bindings to the [Builder](../ui/state-management/arkts-builder.md), is complex and can be performance-intensive. Such frameworks typically leverage ArkUI's layout and event handling, as well as basic node operations and customization capabilities. While most components are customized, some built-in components are needed for mixed display. This is where [FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md) comes into the picture. In the example shown below, while the custom method of **FrameNode** is used for drawing, a built-in component **Column** and its child component **Text** are mounted to the root FrameNode through **BuilderNode**, thereby achieving mixed display.
10
11![en-us_image_frame-node02](figures/frame-node02.png)
12
13This is where [FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md) comes into the picture. **FrameNode** represents an entity node in the component tree. It is used in conjunction with the custom placeholder container component, [NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md), to construct a custom node tree inside the placeholder container. This node tree supports dynamic operations, including node addition, modification, and removal. Basic FrameNodes provide functionality for setting universal attributes and event callbacks, as well as comprehensive customization capabilities for measurement, layout, and rendering.
14
15Moreover, the ArkUI framework enables obtaining and traversing proxy FrameNode objects for built-in components, known as proxy nodes, which facilitate UI tree traversal and allow for obtaining specific information about built-in components or registering additional event listeners.
16
17## Creating and Removing Nodes
18
19You can create and remove nodes with **FrameNode**. You can create a custom instance of **FrameNode** using its constructor, and the instance thereby created corresponds to an entity node. You can use the [dispose](../reference/apis-arkui/js-apis-arkui-frameNode.md#dispose12) API in **FrameNode** to break the binding with the entity node.
20
21> **NOTE**
22>
23> - A valid **UIContext** object is required for creating a FrameNode. If no **UIContext** object is provided or if the provided one is invalid, an exception will be thrown during node creation.
24>
25> - Maintain UI context consistency for custom placeholder components to prevent display issues.
26>
27> - **FrameNode** objects are subject to garbage collection (GC) if not retained.
28
29## Checking Whether a Node is Modifiable
30
31Use [isModifiable](../reference/apis-arkui/js-apis-arkui-frameNode.md#ismodifiable12) to check whether the current node is a proxy for a built-in component. If a FrameNode serves as a proxy, it cannot be modified, which means you cannot change its properties or the structure of its child nodes.
32
33## Obtaining the Corresponding RenderNode
34
35Use the [getRenderNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#getrendernode) API to obtain the RenderNode associated with the FrameNode. You can then perform operations on the obtained RenderNode object to dynamically modify the drawing-related properties of the FrameNode. For details about the properties that can be modified, see [RenderNode](arkts-user-defined-arktsNode-renderNode.md).
36
37> **NOTE**
38>
39> - You cannot obtain the RenderNode for a built-in component's proxy FrameNode.
40>
41> - In **BuilderNode**, you can use [getFrameNode](../reference/apis-arkui/js-apis-arkui-builderNode.md#getframenode) to get the FrameNode object, and then use **getRenderNode** to obtain the RenderNode object of the corresponding root node.
42
43## Operating the Node Tree
44
45With **FrameNode**, you can add, delete, query, and modify nodes, thereby changing the subtree structure of non-proxy nodes. You can also query the parent-child relationships to obtain the results.
46
47> **NOTE**
48>
49> Illegal operations for adding, deleting, or modifying nodes will result in exceptions.
50>
51> Proxy nodes obtained through queries are read-only. They can be used to obtain node information, but cannot modify node properties. These proxy nodes do not hold references to the underlying component entity nodes and therefore do not affect the lifecycle of the corresponding nodes.
52>
53> Node queries only return UI-related nodes and do not include syntax nodes.
54>
55> In scenarios using custom components, you may query and obtain newly added nodes of the custom components, with the node type being **\_\_Common\_\_**.
56
57```ts
58import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI';
59import { BusinessError } from '@kit.BasicServicesKit';
60
61const TEST_TAG: string = "FrameNode"
62
63class Params {
64  text: string = "this is a text"
65}
66
67@Builder
68function buttonBuilder(params: Params) {
69  Column({ space: 10 }) {
70    Button(params.text)
71      .fontSize(12)
72      .borderRadius(8)
73      .borderWidth(2)
74      .backgroundColor(Color.Orange)
75
76    Button(params.text)
77      .fontSize(12)
78      .borderRadius(8)
79      .borderWidth(2)
80      .backgroundColor(Color.Pink)
81  }
82}
83
84class MyNodeController extends NodeController {
85  public buttonNode: BuilderNode<[Params]> | null = null;
86  public frameNode: FrameNode | null = null;
87  public childList: Array<FrameNode> = new Array<FrameNode>();
88  public rootNode: FrameNode | null = null;
89  private uiContext: UIContext | null = null;
90  private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(buttonBuilder);
91
92  makeNode(uiContext: UIContext): FrameNode | null {
93    this.uiContext = uiContext;
94    if (this.rootNode == null) {
95      this.rootNode = new FrameNode(uiContext);
96      this.rootNode.commonAttribute
97        .width("50%")
98        .height(100)
99        .borderWidth(1)
100        .backgroundColor(Color.Gray)
101    }
102
103    if (this.frameNode == null) {
104      this.frameNode = new FrameNode(uiContext);
105      this.frameNode.commonAttribute
106        .width("100%")
107        .height(50)
108        .borderWidth(1)
109        .position({ x: 200, y: 0 })
110        .backgroundColor(Color.Pink);
111      this.rootNode.appendChild(this.frameNode);
112    }
113    if (this.buttonNode == null) {
114      this.buttonNode = new BuilderNode<[Params]>(uiContext);
115      this.buttonNode.build(this.wrapBuilder, { text: "This is a Button" })
116      this.rootNode.appendChild(this.buttonNode.getFrameNode())
117    }
118    return this.rootNode;
119  }
120
121  operationFrameNodeWithFrameNode(frameNode: FrameNode | undefined | null) {
122    if (frameNode) {
123      console.log(TEST_TAG + " get ArkTSNode success.")
124      console.log(TEST_TAG + " check rootNode whether is modifiable " + frameNode.isModifiable());
125    }
126    if (this.uiContext) {
127      let frameNode1 = new FrameNode(this.uiContext);
128      let frameNode2 = new FrameNode(this.uiContext);
129      frameNode1.commonAttribute.size({ width: 50, height: 50 })
130        .backgroundColor(Color.Black)
131        .position({ x: 50, y: 60 })
132      frameNode2.commonAttribute.size({ width: 50, height: 50 })
133        .backgroundColor(Color.Orange)
134        .position({ x: 120, y: 60 })
135      try {
136        frameNode?.appendChild(frameNode1);
137        console.log(TEST_TAG + " appendChild success ");
138      } catch (err) {
139        console.log(TEST_TAG + " appendChild fail :" + (err as BusinessError).code + " : " +
140        (err as BusinessError).message);
141      }
142      try {
143        frameNode?.insertChildAfter(frameNode2, null);
144        console.log(TEST_TAG + " insertChildAfter success ");
145      } catch (err) {
146        console.log(TEST_TAG + " insertChildAfter fail : " + (err as BusinessError).code + " : " +
147        (err as BusinessError).message);
148      }
149      setTimeout(() => {
150        try {
151          frameNode?.removeChild(frameNode?.getChild(0))
152          console.log(TEST_TAG + " removeChild success ");
153        } catch (err) {
154          console.log(TEST_TAG + " removeChild fail : " + (err as BusinessError).code + " : " +
155          (err as BusinessError).message);
156        }
157      }, 2000)
158      setTimeout(() => {
159        try {
160          frameNode?.clearChildren();
161          console.log(TEST_TAG + " clearChildren success ");
162        } catch (err) {
163          console.log(TEST_TAG + " clearChildren fail : " + (err as BusinessError).code + " : " +
164          (err as BusinessError).message);
165        }
166      }, 4000)
167    }
168  }
169
170  testInterfaceAboutSearch(frameNode: FrameNode | undefined | null): string {
171    let result: string = "";
172    if (frameNode) {
173      result = result + `current node is ${frameNode.getNodeType()} \n`;
174      result = result + `parent node is ${frameNode.getParent()?.getNodeType()} \n`;
175      result = result + `child count is ${frameNode.getChildrenCount()} \n`;
176      result = result + `first child node is ${frameNode.getFirstChild()?.getNodeType()} \n`;
177      result = result + `second child node is ${frameNode.getChild(1)?.getNodeType()} \n`;
178      result = result + `previousSibling node is ${frameNode.getPreviousSibling()?.getNodeType()} \n`;
179      result = result + `nextSibling node is ${frameNode.getNextSibling()?.getNodeType()} \n`;
180    }
181    return result;
182  }
183
184  checkAppendChild(parent: FrameNode | undefined | null, child: FrameNode | undefined | null) {
185    try {
186      if (parent && child) {
187        parent.appendChild(child);
188        console.log(TEST_TAG + " appendChild success ");
189      }
190    } catch (err) {
191      console.log(TEST_TAG + " appendChild fail : " + (err as BusinessError).code + " : " +
192      (err as BusinessError).message);
193    }
194  }
195}
196
197@Entry
198@Component
199struct Index {
200  @State index: number = 0;
201  @State result: string = ""
202  private myNodeController: MyNodeController = new MyNodeController();
203
204  build() {
205    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
206      List({ space: 20, initialIndex: 0 }) {
207        ListItem() {
208          Column({ space: 5 }) {
209            Text("Verify the add, delete, and modify features of the FrameNode")
210            Button("Operate Custom FrameNode")
211              .fontSize(16)
212              .width(400)
213              .onClick(() => {
214                // Add, delete, and modify FrameNode child nodes, which is properly implemented.
215                this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.frameNode);
216              })
217            Button("Operate Proxy Node in BuilderNode")
218              .fontSize(16)
219              .width(400)
220              .onClick(() => {
221                // Add, delete, and modify the BuilderNode proxy node to generate an exception.
222                this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.buttonNode?.getFrameNode());
223              })
224            Button("Operate Proxy Node in Built-in Component")
225              .fontSize(16)
226              .width(400)
227              .onClick(() => {
228                // Add, delete, and modify the proxy node to generate an exception.
229                this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.rootNode?.getParent());
230              })
231          }
232        }
233
234        ListItem() {
235          Column({ space: 5 }) {
236            Text("Verify the feature to add subnodes to FrameNode")
237            Button("Add BuilderNode Proxy Node")
238              .fontSize(16)
239              .width(400)
240              .onClick(() => {
241                let buttonNode = new BuilderNode<[Params]>(this.getUIContext());
242                buttonNode.build(wrapBuilder<[Params]>(buttonBuilder), { text: "BUTTON" })
243                this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, buttonNode?.getFrameNode());
244              })
245            Button("Add Built-in Component Proxy Node")
246              .fontSize(16)
247              .width(400)
248              .onClick(() => {
249                this.myNodeController.checkAppendChild(this.myNodeController?.frameNode,
250                  this.myNodeController?.rootNode?.getParent());
251              })
252            Button("Add Custom Node with Existing Parent Node")
253              .fontSize(16)
254              .width(400)
255              .onClick(() => {
256                this.myNodeController.checkAppendChild(this.myNodeController?.frameNode,
257                  this.myNodeController?.rootNode);
258              })
259          }
260        }
261
262        ListItem() {
263          Column({ space: 5 }) {
264            Text("Verify the query feature of the FrameNode")
265            Button("Operate Custom FrameNode")
266              .fontSize(16)
267              .width(400)
268              .onClick(() => {
269                // Query the FrameNode. The current node is a child of the NodeContainer.
270                this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode);
271                setTimeout(() => {
272                  // Query the FrameNode. The current node is the first child node under rootNode.
273                  this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.frameNode);
274                }, 2000)
275              })
276            Button("Operate Proxy Node in BuilderNode")
277              .fontSize(16)
278              .width(400)
279              .onClick(() => {
280                // Query the BuilderNode proxy nodes. The current node is the Column node within BuilderNode.
281                this.result =
282                  this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.buttonNode?.getFrameNode());
283              })
284            Button("Operate Proxy Node in Built-in Component")
285              .fontSize(16)
286              .width(400)
287              .onClick(() => {
288                // Query the proxy node. The current node is the NodeContainer.
289                this.result =
290                  this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode?.getParent());
291              })
292          }
293        }
294      }.height("50%")
295
296      Text(`Result: \n${this.result}`)
297        .fontSize(16)
298        .width(400)
299        .height(200)
300        .padding(30)
301        .borderWidth(1)
302      Column() {
303        Text("This is a NodeContainer.")
304          .textAlign(TextAlign.Center)
305          .borderRadius(10)
306          .backgroundColor(0xFFFFFF)
307          .width('100%')
308          .fontSize(16)
309        NodeContainer(this.myNodeController)
310          .borderWidth(1)
311          .width(400)
312          .height(150)
313      }
314    }
315    .padding({
316      left: 35,
317      right: 35,
318      top: 35,
319      bottom: 35
320    })
321    .width("100%")
322    .height("100%")
323  }
324}
325```
326
327## Setting Universal Attributes and Event Callbacks
328
329Use the [commonAttribute](../reference/apis-arkui/js-apis-arkui-frameNode.md#commonattribute12) and [commonEvent](../reference/apis-arkui/js-apis-arkui-frameNode.md#commonevent12) objects to set the [universal attributes](../reference/apis-arkui/arkui-ts/ts-universal-attributes-size.md) and [event callbacks](../reference/apis-arkui/arkui-ts/ts-uicommonevent.md), respectively.
330
331> **NOTE**
332>
333> - Proxy node attributes are immutable. Therefore, **commonAttribute** is ineffective on proxy nodes.
334>
335> - The custom basic events that you define run in parallel with the events predefined in the built-in components, without overriding them. When two event callbacks are set, the built-in component event callback is prioritized.
336
337```ts
338import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI'
339
340class Params {
341  text: string = "this is a text"
342}
343
344@Builder
345function buttonBuilder(params: Params) {
346  Button(params.text)
347    .fontSize(12)
348    .borderRadius(8)
349    .borderWidth(2)
350    .backgroundColor(Color.Orange)
351    .onClick((event: ClickEvent) => {
352      console.log(`Button ${JSON.stringify(event)}`);
353    })
354}
355
356class MyNodeController extends NodeController {
357  public buttonNode: BuilderNode<[Params]> | null = null;
358  public frameNode: FrameNode | null = null;
359  public rootNode: FrameNode | null = null;
360  private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(buttonBuilder);
361
362  makeNode(uiContext: UIContext): FrameNode | null {
363    if (this.rootNode == null) {
364      this.rootNode = new FrameNode(uiContext);
365      // Modify the attributes of rootNode, which is a custom FrameNode, and the changes take effect.
366      this.rootNode.commonAttribute
367        .width("100%")
368        .height(100)
369        .borderWidth(1)
370        .backgroundColor(Color.Gray)
371    }
372
373    if (this.frameNode == null) {
374      this.frameNode = new FrameNode(uiContext);
375      // Modify the attributes of frameNode, which is a custom FrameNode, and the changes take effect.
376      this.frameNode.commonAttribute
377        .width("50%")
378        .height(50)
379        .borderWidth(1)
380        .backgroundColor(Color.Pink);
381      this.rootNode.appendChild(this.frameNode);
382    }
383    if (this.buttonNode == null) {
384      this.buttonNode = new BuilderNode<[Params]>(uiContext);
385      this.buttonNode.build(this.wrapBuilder, { text: "This is a Button" })
386      // Modify the attributes of the FrameNode obtained from BuilderNode, which is not a custom FrameNode, and the changes do not take effect.
387      this.buttonNode?.getFrameNode()?.commonAttribute.position({ x: 100, y: 100 })
388      this.rootNode.appendChild(this.buttonNode.getFrameNode())
389    }
390    return this.rootNode;
391  }
392
393  modifyNode(frameNode: FrameNode | null | undefined, sizeValue: SizeOptions, positionValue: Position) {
394    if (frameNode) {
395      frameNode.commonAttribute.size(sizeValue).position(positionValue);
396    }
397  }
398
399  addClickEvent(frameNode: FrameNode | null | undefined) {
400    if (frameNode) {
401      frameNode.commonEvent.setOnClick((event: ClickEvent) => {
402        console.log(`FrameNode ${JSON.stringify(event)}`);
403      })
404    }
405  }
406}
407
408@Entry
409@Component
410struct Index {
411  private myNodeController: MyNodeController = new MyNodeController();
412
413  build() {
414    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
415      Column({ space: 10 }) {
416        Text("Modify the universal node attributes: width and height.")
417        Button("modify ArkTS-FrameNode")
418          .onClick(() => {
419            // The object obtained is the FrameNode created on the current page, which can be modified. That is, the size and position of the node can be changed.
420            console.log("Check the weather the node can be modified " + this.myNodeController?.frameNode
421            ?.isModifiable());
422            this.myNodeController.modifyNode(this.myNodeController?.frameNode, { width: 150, height: 100 }, {
423              x: 100,
424              y: 0
425            })
426          })
427        Button("modify FrameNode get by BuilderNode")
428          .onClick(() => {
429            // The object obtained is the root node of the BuilderNode on the current page, which cannot be modified. That is, the size and position of the node remain unchanged.
430            console.log("Check the weather the node can be modified " +
431            this.myNodeController?.buttonNode?.getFrameNode()
432            ?.isModifiable());
433            this.myNodeController.modifyNode(this.myNodeController?.buttonNode?.getFrameNode(), {
434              width: 100,
435              height: 100
436            }, { x: 50, y: 50 })
437          })
438        Button("modify proxyFrameNode get by search")
439          .onClick(() => {
440            // The rootNode object calling getParent() obtains the NodeContainer node on the current page, which cannot be modified. That is, the size and position of the node remain unchanged.
441            console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent()
442            ?.isModifiable());
443            this.myNodeController.modifyNode(this.myNodeController?.rootNode?.getParent(), {
444              width: 500,
445              height: 500
446            }, {
447              x: 0,
448              y: 0
449            })
450          })
451      }
452      .padding({
453        left: 35,
454        right: 35,
455        top: 35,
456        bottom: 35
457      })
458
459      Column({ space: 10 }) {
460        Text("Modify the node click event.")
461        Button("add click event to ArkTS-FrameNode")
462          .onClick(() => {
463            // The object obtained is the FrameNode created on the current page, to which click events can be added.
464            // The added click event participates in event competition, meaning the click event will be consumed by this node and will no longer bubble up to the parent component.
465            console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent()
466            ?.isModifiable());
467            this.myNodeController.addClickEvent(this.myNodeController?.frameNode)
468          })
469        Button("add click event to FrameNode get by BuilderNode")
470          .onClick(() => {
471            // The object obtained is the root node of the BuilderNode on the current page, to which click events can be added.
472            // When the button is clicked, the click event callback set through the built-in component API is called first, followed by the click listener added through commonEvent.
473            console.log("Check the weather the node can be modified " +
474            this.myNodeController?.buttonNode?.getFrameNode()
475            ?.isModifiable());
476            this.myNodeController.addClickEvent(this.myNodeController?.buttonNode?.getFrameNode())
477          })
478        Button("add click event to proxyFrameNode get by search")
479          .onClick(() => {
480            // The rootNode object calling getParent() obtains the NodeContainer node on the current page, to which click events can be added.
481            console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent()
482            ?.isModifiable());
483            this.myNodeController.addClickEvent(this.myNodeController?.rootNode?.getParent());
484          })
485      }
486      .padding({
487        left: 35,
488        right: 35,
489        top: 35,
490        bottom: 35
491      })
492
493      NodeContainer(this.myNodeController)
494        .borderWidth(1)
495        .width("100%")
496        .height(100)
497        .onClick((event: ClickEvent) => {
498          console.log(`NodeContainer ${JSON.stringify(event)}`);
499        })
500    }
501    .padding({
502      left: 35,
503      right: 35,
504      top: 35,
505      bottom: 35
506    })
507    .width("100%")
508    .height("100%")
509  }
510}
511```
512
513## Implementing Custom Measurement, Layout, and Drawing
514
515By overriding the [onDraw](../reference/apis-arkui/js-apis-arkui-frameNode.md#ondraw12) API, you can customize the drawing content of the FrameNode. Use the [invalidate](../reference/apis-arkui/js-apis-arkui-frameNode.md#invalidate12) API to manually trigger a redraw of the node.
516
517By overriding the [onMeasure](../reference/apis-arkui/js-apis-arkui-frameNode.md#onmeasure12) API, you can customize how the FrameNode measures its size. Use [measure](../reference/apis-arkui/js-apis-arkui-frameNode.md#measure12) to proactively pass layout constraints to initiate a remeasurement.
518
519By overriding the [onLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#onlayout12) API, you can customize the layout of the FrameNode. Use [layout](../reference/apis-arkui/js-apis-arkui-frameNode.md#layout12) to proactively pass position information and initiate a re-layout.
520
521Use [setNeedsLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#setneedslayout12) to mark the current node and trigger a re-layout in the next frame.
522
523> **NOTE**
524>
525> - After a node is disposed and unbound, the FrameNode no longer represents an entity node. In this case, the **invalidate** call cannot update the previously bound node.
526>
527> - Custom drawings made through the **onDraw** API cannot exceed the component's size.
528
529```ts
530import { DrawContext, FrameNode, NodeController, Position, Size, UIContext, LayoutConstraint } from '@kit.ArkUI';
531import { drawing } from '@kit.ArkGraphics2D';
532
533function GetChildLayoutConstraint(constraint: LayoutConstraint, child: FrameNode): LayoutConstraint {
534  const size = child.getUserConfigSize();
535  const width = Math.max(
536    Math.min(constraint.maxSize.width, size.width.value),
537    constraint.minSize.width
538  );
539  const height = Math.max(
540    Math.min(constraint.maxSize.height, size.height.value),
541    constraint.minSize.height
542  );
543  const finalSize: Size = { width, height };
544  const res: LayoutConstraint = {
545    maxSize: finalSize,
546    minSize: finalSize,
547    percentReference: finalSize
548  };
549
550  return res;
551}
552
553class MyFrameNode extends FrameNode {
554  public width: number = 100;
555  public offsetY: number = 0;
556  private space: number = 1;
557
558  onMeasure(constraint: LayoutConstraint): void {
559    let sizeRes: Size = { width: vp2px(100), height: vp2px(100) };
560    for (let i = 0; i < this.getChildrenCount(); i++) {
561      let child = this.getChild(i);
562      if (child) {
563        let childConstraint = GetChildLayoutConstraint(constraint, child);
564        child.measure(childConstraint);
565        let size = child.getMeasuredSize();
566        sizeRes.height += size.height + this.space;
567        sizeRes.width = Math.max(sizeRes.width, size.width);
568      }
569    }
570    this.setMeasuredSize(sizeRes);
571  }
572
573  onLayout(position: Position): void {
574    for (let i = 0; i < this.getChildrenCount(); i++) {
575      let child = this.getChild(i);
576      if (child) {
577        child.layout({
578          x: vp2px(100),
579          y: vp2px(this.offsetY)
580        });
581        let layoutPosition = child.getLayoutPosition();
582        console.log("child position:" + JSON.stringify(layoutPosition));
583      }
584    }
585    this.setLayoutPosition(position);
586  }
587
588  onDraw(context: DrawContext) {
589    const canvas = context.canvas;
590    const pen = new drawing.Pen();
591    pen.setStrokeWidth(15);
592    pen.setColor({
593      alpha: 255,
594      red: 255,
595      green: 0,
596      blue: 0
597    });
598    canvas.attachPen(pen);
599    canvas.drawRect({
600      left: 50,
601      right: this.width + 50,
602      top: 50,
603      bottom: this.width + 50,
604    });
605    canvas.detachPen();
606  }
607
608  addWidth() {
609    this.width = (this.width + 10) % 50 + 100;
610  }
611}
612
613class MyNodeController extends NodeController {
614  public rootNode: MyFrameNode | null = null;
615
616  makeNode(context: UIContext): FrameNode | null {
617    this.rootNode = new MyFrameNode(context);
618    this.rootNode?.commonAttribute?.size({ width: 100, height: 100 }).backgroundColor(Color.Green);
619    let frameNode: FrameNode = new FrameNode(context);
620    this.rootNode.appendChild(frameNode);
621    frameNode.commonAttribute.width(10).height(10).backgroundColor(Color.Pink);
622    return this.rootNode;
623  }
624}
625
626@Entry
627@Component
628struct Index {
629  private nodeController: MyNodeController = new MyNodeController();
630
631  build() {
632    Row() {
633      Column() {
634        NodeContainer(this.nodeController)
635          .width('100%')
636          .height(200)
637          .backgroundColor('#FFF0F0F0')
638        Button('Invalidate')
639          .margin(10)
640          .onClick(() => {
641            this.nodeController?.rootNode?.addWidth();
642            this.nodeController?.rootNode?.invalidate();
643          })
644        Button('UpdateLayout')
645          .onClick(() => {
646            let node = this.nodeController.rootNode;
647            node!.offsetY = (node!.offsetY + 10) % 110;
648            this.nodeController?.rootNode?.setNeedsLayout();
649          })
650      }
651      .width('100%')
652      .height('100%')
653    }
654    .height('100%')
655  }
656}
657```
658
659## Searching for Nodes and Obtaining Basic Information
660
661**FrameNode** provides APIs for obtaining basic information about an entity node. For details about the returned information, see the FrameNode API documentation.
662
663To obtain a FrameNode, use any of the following methods:
664
6651. Use [getFrameNodeById](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyid12).
666
6672. Use [getFrameNodeByUniqueId](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyuniqueid12).
668
6693. Use an [observer](../reference/apis-arkui/js-apis-arkui-observer.md).
670
671> **NOTE**
672>
673> 1. Currently, the following information can be obtained:
674>
675> - Node size: [getMeasuredSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getmeasuredsize12), [getUserConfigSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigsize12)
676>
677> - Layout information: [getPositionToWindow](../reference/apis-arkui/js-apis-arkui-frameNode.md#getpositiontowindow12), [getPositionToParent](../reference/apis-arkui/js-apis-arkui-frameNode.md#getpositiontoparent12), [getLayoutPosition](../reference/apis-arkui/js-apis-arkui-frameNode.md#getlayoutposition12), [getUserConfigBorderWidth](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigborderwidth12), [getUserConfigPadding](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigpadding12), [getUserConfigMargin](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigmargin12)
678>
679> - Node information: [getId](../reference/apis-arkui/js-apis-arkui-frameNode.md#getid12), [getUniqueId](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuniqueid12), [getNodeType](../reference/apis-arkui/js-apis-arkui-frameNode.md#getnodetype12), [getOpacity](../reference/apis-arkui/js-apis-arkui-frameNode.md#getopacity12), [isVisible](../reference/apis-arkui/js-apis-arkui-frameNode.md#isvisible12), [isClipToFrame](../reference/apis-arkui/js-apis-arkui-frameNode.md#iscliptoframe12), [isAttached](../reference/apis-arkui/js-apis-arkui-frameNode.md#isattached12), [getInspectorInfo](../reference/apis-arkui/js-apis-arkui-frameNode.md#getinspectorinfo12), [getCustomProperty](../reference/apis-arkui/js-apis-arkui-frameNode.md#getcustomproperty12)
680>
681> 2. UINode-type nodes, such as JsView nodes, [Span](../../application-dev/reference/apis-arkui/arkui-ts/ts-basic-components-span.md), [ContainerSpan](../../application-dev/reference/apis-arkui/arkui-ts/ts-basic-components-containerspan.md), [ContentSlot](../../application-dev/reference/apis-arkui/arkui-ts/ts-components-contentSlot.md), [ForEach](../../application-dev/reference/apis-arkui/arkui-ts/ts-rendering-control-foreach.md), [LazyForEach](../../application-dev/reference/apis-arkui/arkui-ts/ts-rendering-control-lazyforeach.md), and **if/else** components, cannot be obtained.
682
683## Obtaining Node Position Offset Information
684
685**FrameNode** provides APIs to obtain the position offsets of nodes relative to the window, parent component, and the screen: [getPositionToWindow](../reference/apis-arkui/js-apis-arkui-frameNode.md#getpositiontowindow12), [getPositionToParent](../reference/apis-arkui/js-apis-arkui-frameNode.md#getpositiontoparent12), [getPositionToScreen](../reference/apis-arkui/js-apis-arkui-frameNode.md#getpositiontoscreen12), [getPositionToWindowWithTransform](../reference/apis-arkui/js-apis-arkui-frameNode.md#getpositiontowindowwithtransform12), [getPositionToParentWithTransform](../reference/apis-arkui/js-apis-arkui-frameNode.md#getpositiontoparentwithtransform12), [getPositionToScreenWithTransform](../reference/apis-arkui/js-apis-arkui-frameNode.md#getpositiontoscreenwithtransform12), [getLayoutPosition](../reference/apis-arkui/js-apis-arkui-frameNode.md#getlayoutposition12), [getUserConfigBorderWidth](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigborderwidth12), [getUserConfigPadding](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigpadding12), [getUserConfigMargin](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigmargin12).
686
687```ts
688import { NodeController, FrameNode, UIContext } from '@kit.ArkUI';
689
690const TEST_TAG: string = "FrameNode"
691
692class MyNodeController extends NodeController {
693  public frameNode: FrameNode | null = null;
694  private rootNode: FrameNode | null = null;
695
696  makeNode(uiContext: UIContext): FrameNode | null {
697    this.rootNode = new FrameNode(uiContext);
698    this.frameNode = new FrameNode(uiContext);
699    this.rootNode.appendChild(this.frameNode);
700    return this.rootNode;
701  }
702
703  getPositionToWindow() {
704    let positionToWindow = this.rootNode?.getPositionToWindow(); // Obtain the position offset of the FrameNode relative to the window.
705    console.log(`${TEST_TAG} ${JSON.stringify(positionToWindow)}`);
706  }
707
708  getPositionToParent() {
709    let positionToParent = this.rootNode?.getPositionToParent(); // Obtain the position offset of the FrameNode relative to the parent component.
710    console.log(`${TEST_TAG} ${JSON.stringify(positionToParent)}`);
711  }
712
713  getPositionToScreen() {
714    let positionToScreen = this.rootNode?.getPositionToScreen(); // Obtain the position offset of the FrameNode relative to the screen.
715    console.log(`${TEST_TAG} ${JSON.stringify(positionToScreen)}`);
716  }
717
718  getPositionToWindowWithTransform() {
719    let positionToWindowWithTransform =
720      this.rootNode?.getPositionToWindowWithTransform(); // Obtain the position offset of the FrameNode relative to the window with drawing attributes.
721    console.log(`${TEST_TAG} ${JSON.stringify(positionToWindowWithTransform)}`);
722  }
723
724  getPositionToParentWithTransform() {
725    let positionToParentWithTransform =
726      this.rootNode?.getPositionToParentWithTransform(); // Obtain the position offset of the FrameNode relative to the parent component with drawing attributes.
727    console.log(`${TEST_TAG} ${JSON.stringify(positionToParentWithTransform)}`);
728  }
729
730  getPositionToScreenWithTransform() {
731    let positionToScreenWithTransform =
732      this.rootNode?.getPositionToScreenWithTransform(); // Obtain the position offset of the FrameNode relative to the screen with drawing attributes.
733    console.log(`${TEST_TAG} ${JSON.stringify(positionToScreenWithTransform)}`);
734  }
735}
736
737@Entry
738@Component
739struct Index {
740  private myNodeController: MyNodeController = new MyNodeController();
741
742  build() {
743    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
744      Button("getPositionToWindow")
745        .width(300)
746        .onClick(() => {
747          this.myNodeController.getPositionToWindow();
748        })
749      Button("getPositionToParent")
750        .width(300)
751        .onClick(() => {
752          this.myNodeController.getPositionToParent();
753        })
754      Button("getPositionToScreen")
755        .width(300)
756        .onClick(() => {
757          this.myNodeController.getPositionToScreen();
758        })
759      Button("getPositionToParentWithTransform")
760        .width(300)
761        .onClick(() => {
762          this.myNodeController.getPositionToParentWithTransform();
763        })
764      Button("getPositionToWindowWithTransform")
765        .width(300)
766        .onClick(() => {
767          this.myNodeController.getPositionToWindowWithTransform();
768        })
769      Button("getPositionToScreenWithTransform")
770        .width(300)
771        .onClick(() => {
772          this.myNodeController.getPositionToScreenWithTransform();
773        })
774      Column() {
775        Text("This is a NodeContainer.")
776          .textAlign(TextAlign.Center)
777          .borderRadius(10)
778          .backgroundColor(0xFFFFFF)
779          .width('100%')
780          .fontSize(16)
781        NodeContainer(this.myNodeController)
782          .borderWidth(1)
783          .width(300)
784          .height(100)
785      }
786    }
787    .padding({
788      left: 35,
789      right: 35,
790      top: 35,
791      bottom: 35
792    })
793    .width("100%")
794    .height("100%")
795  }
796}
797```
798
799## Creating a FrameNode of a Specific Type Using typeNode
800
801By creating a FrameNode of a specific type using **typeNode**, you can obtain user-set attribute information through attribute obtaining APIs.
802
803```ts
804import { NodeController, FrameNode, UIContext, BuilderNode, typeNode } from '@kit.ArkUI';
805
806class Params {
807  text: string = "";
808
809  constructor(text: string) {
810    this.text = text;
811  }
812}
813
814@Builder
815function buildText(params: Params) {
816  Column() {
817    Text(params.text)
818      .id("buildText")
819      .border({ width: 1 })
820      .padding(1)
821      .fontSize(25)
822      .fontWeight(FontWeight.Bold)
823      .margin({ top: 10 })
824      .visibility(Visibility.Visible)
825      .opacity(0.7)
826      .customProperty("key1", "value1")
827      .width(300)
828  }
829}
830
831const TEST_TAG: string = "FrameNode"
832
833class MyNodeController extends NodeController {
834  public frameNode: typeNode.Column | null = null;
835  public uiContext: UIContext | undefined = undefined;
836  private rootNode: FrameNode | null = null;
837  private textNode: BuilderNode<[Params]> | null = null;
838  public textTypeNode: typeNode.Text | null = null;
839  private message: string = "DEFAULT";
840
841  makeNode(uiContext: UIContext): FrameNode | null {
842    this.rootNode = new FrameNode(uiContext);
843    this.uiContext = uiContext;
844    this.frameNode = typeNode.createNode(uiContext, "Column");
845    this.frameNode.attribute
846      .width("100%")
847      .height("100%")
848    this.rootNode.appendChild(this.frameNode);
849    this.textNode = new BuilderNode(uiContext);
850    this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message));
851    this.frameNode.appendChild(this.textNode.getFrameNode());
852    this.textTypeNode = typeNode.createNode(uiContext, "Text");
853    this.textTypeNode.initialize("textTypeNode")
854      .fontSize(25)
855      .visibility(Visibility.Visible)
856      .id("textTypeNode")
857    this.frameNode.appendChild(this.textTypeNode);
858    return this.rootNode;
859  }
860
861  removeChild(frameNode: FrameNode) {
862    let parent = frameNode.getParent();
863    if (parent) {
864      parent.removeChild(frameNode);
865
866    }
867  }
868
869  getUserConfigBorderWidth(frameNode: FrameNode) {
870    let userConfigBorderWidth = frameNode?.getUserConfigBorderWidth(); // Obtain the border width set by the user.
871    console.log(`${TEST_TAG} ${JSON.stringify(userConfigBorderWidth)}`);
872  }
873
874  getUserConfigPadding(frameNode: FrameNode) {
875    let userConfigPadding = frameNode?.getUserConfigPadding(); // Obtain the padding set by the user.
876    console.log(`${TEST_TAG} ${JSON.stringify(userConfigPadding)}`);
877  }
878
879  getUserConfigMargin(frameNode: FrameNode) {
880    let userConfigMargin = frameNode?.getUserConfigMargin(); // Obtain the margin set by the user.
881    console.log(`${TEST_TAG} ${JSON.stringify(userConfigMargin)}`);
882  }
883
884  getUserConfigSize(frameNode: FrameNode) {
885    let userConfigSize = frameNode?.getUserConfigSize(); // Obtain the width and height set by the user.
886    console.log(`${TEST_TAG} ${JSON.stringify(userConfigSize)}`);
887  }
888
889  getId(frameNode: FrameNode) {
890    let id = frameNode?.getId(); // Obtain the node ID set by the user.
891    console.log(`${TEST_TAG} ${id}`);
892  }
893
894  getUniqueId(frameNode: FrameNode) {
895    let uniqueId = frameNode?.getUniqueId(); // Obtain the unique node ID allocated by the system.
896    console.log(`${TEST_TAG} ${uniqueId}`);
897  }
898
899  getNodeType(frameNode: FrameNode) {
900    let nodeType = frameNode?.getNodeType(); // Obtain the node type.
901    console.log(`${TEST_TAG} ${nodeType}`);
902  }
903
904  getOpacity(frameNode: FrameNode) {
905    let opacity = frameNode?.getOpacity(); // Obtain the node opacity.
906    console.log(`${TEST_TAG} ${JSON.stringify(opacity)}`);
907  }
908
909  isVisible(frameNode: FrameNode) {
910    let visible = frameNode?.isVisible(); // Obtain whether the node is visible.
911    console.log(`${TEST_TAG} ${JSON.stringify(visible)}`);
912  }
913
914  isClipToFrame(frameNode: FrameNode) {
915    let clipToFrame = frameNode?.isClipToFrame(); // Obtain whether the node is clipped to the component area.
916    console.log(`${TEST_TAG} ${JSON.stringify(clipToFrame)}`);
917  }
918
919  isAttached(frameNode: FrameNode) {
920    let attached = frameNode?.isAttached(); // Obtain whether a node is mounted to the main node tree.
921    console.log(`${TEST_TAG} ${JSON.stringify(attached)}`);
922  }
923
924  getInspectorInfo(frameNode: FrameNode) {
925    let inspectorInfo = frameNode?.getInspectorInfo(); // Obtain the structure information of the node.
926    console.log(`${TEST_TAG} ${JSON.stringify(inspectorInfo)}`);
927  }
928}
929
930@Entry
931@Component
932struct Index {
933  private myNodeController: MyNodeController = new MyNodeController();
934  @State index: number = 0;
935
936  build() {
937    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
938      Column() {
939        Text("This is a NodeContainer.")
940          .textAlign(TextAlign.Center)
941          .borderRadius(10)
942          .backgroundColor(0xFFFFFF)
943          .width('100%')
944          .fontSize(16)
945        NodeContainer(this.myNodeController)
946          .borderWidth(1)
947          .width(300)
948          .height(100)
949      }
950
951      Button("getUserConfigBorderWidth")
952        .width(300)
953        .onClick(() => {
954          const uiContext: UIContext = this.getUIContext();
955          if (uiContext) {
956            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
957            if (node) {
958              this.myNodeController.getUserConfigBorderWidth(node);
959            }
960          }
961        })
962      Button("getUserConfigPadding")
963        .width(300)
964        .onClick(() => {
965          const uiContext: UIContext = this.getUIContext();
966          if (uiContext) {
967            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
968            if (node) {
969              this.myNodeController.getUserConfigPadding(node);
970            }
971          }
972        })
973      Button("getUserConfigMargin")
974        .width(300)
975        .onClick(() => {
976          const uiContext: UIContext = this.getUIContext();
977          if (uiContext) {
978            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
979            if (node) {
980              this.myNodeController.getUserConfigMargin(node);
981            }
982          }
983        })
984      Button("getUserConfigSize")
985        .width(300)
986        .onClick(() => {
987          const uiContext: UIContext = this.getUIContext();
988          if (uiContext) {
989            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
990            if (node) {
991              this.myNodeController.getUserConfigSize(node);
992            }
993          }
994        })
995      Button("getId")
996        .width(300)
997        .onClick(() => {
998          const uiContext: UIContext = this.getUIContext();
999          if (uiContext) {
1000            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
1001            if (node) {
1002              this.myNodeController.getId(node);
1003            }
1004          }
1005        })
1006      Button("getUniqueId")
1007        .width(300)
1008        .onClick(() => {
1009          const uiContext: UIContext = this.getUIContext();
1010          if (uiContext) {
1011            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
1012            if (node) {
1013              this.myNodeController.getUniqueId(node);
1014            }
1015          }
1016        })
1017      Button("getNodeType")
1018        .width(300)
1019        .onClick(() => {
1020          const uiContext: UIContext = this.getUIContext();
1021          if (uiContext) {
1022            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
1023            if (node) {
1024              this.myNodeController.getNodeType(node);
1025            }
1026          }
1027        })
1028      Button("getOpacity")
1029        .width(300)
1030        .onClick(() => {
1031          const uiContext: UIContext = this.getUIContext();
1032          if (uiContext) {
1033            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
1034            if (node) {
1035              this.myNodeController.getOpacity(node);
1036            }
1037          }
1038        })
1039      Button("isVisible")
1040        .width(300)
1041        .onClick(() => {
1042          const uiContext: UIContext = this.getUIContext();
1043          if (uiContext) {
1044            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
1045            if (node) {
1046              this.myNodeController.isVisible(node);
1047            }
1048          }
1049        })
1050      Button("isClipToFrame")
1051        .width(300)
1052        .onClick(() => {
1053          const uiContext: UIContext = this.getUIContext();
1054          if (uiContext) {
1055            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
1056            if (node) {
1057              this.myNodeController.isClipToFrame(node);
1058            }
1059          }
1060        })
1061      Button("isAttached")
1062        .width(300)
1063        .onClick(() => {
1064          const uiContext: UIContext = this.getUIContext();
1065          if (uiContext) {
1066            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
1067            if (node) {
1068              this.myNodeController.isAttached(node);
1069            }
1070          }
1071        })
1072      Button("remove Text")
1073        .width(300)
1074        .onClick(() => {
1075          const uiContext: UIContext = this.getUIContext();
1076          if (uiContext) {
1077            const node: FrameNode | null = uiContext.getFrameNodeById("textTypeNode") || null;
1078            if (node) {
1079              this.myNodeController.removeChild(node);
1080              this.myNodeController.isAttached(node);
1081            }
1082          }
1083        })
1084      Button("getInspectorInfo")
1085        .width(300)
1086        .onClick(() => {
1087          const uiContext: UIContext = this.getUIContext();
1088          if (uiContext) {
1089            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
1090            if (node) {
1091              this.myNodeController.getInspectorInfo(node);
1092            }
1093          }
1094        })
1095      Button("getCustomProperty")
1096        .width(300)
1097        .onClick(() => {
1098          const uiContext: UIContext = this.getUIContext();
1099          if (uiContext) {
1100            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
1101            if (node) {
1102              const property = node.getCustomProperty("key1");
1103              console.log(TEST_TAG, JSON.stringify(property));
1104            }
1105          }
1106        })
1107    }
1108    .padding({
1109      left: 35,
1110      right: 35,
1111      top: 35,
1112      bottom: 35
1113    })
1114    .width("100%")
1115    .height("100%")
1116  }
1117}
1118```
1119
1120## Disassociating the Current FrameNode Object from the Entity FrameNode
1121
1122To disassociate the current **FrameNode** object from the entity FrameNode, call the [dispose](../reference/apis-arkui/js-apis-arkui-frameNode.md#dispose12) API.
1123
1124> **NOTE**
1125>
1126> After the **dispose** API is called, the **FrameNode** object no longer corresponds to any actual FrameNode. In this case, any attempt to call the following APIs will result in a JS crash in the application: **getMeasuredSize**, **getLayoutPosition**, **getUserConfigBorderWidth**, **getUserConfigPadding**, **getUserConfigMargin**, **getUserConfigSize**.
1127>
1128> To check whether the current **FrameNode** object corresponds to an entity FrameNode, you can use [getUniqueId](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuniqueid12) API. A **UniqueId** value greater than 0 indicates that the object is associated with an entity FrameNode.
1129
1130```ts
1131import { NodeController, FrameNode, BuilderNode } from '@kit.ArkUI';
1132
1133const TEST_TAG: string = "FrameNode";
1134
1135@Component
1136struct TestComponent {
1137  build() {
1138    Column() {
1139      Text('This is a BuilderNode.')
1140        .fontSize(16)
1141        .fontWeight(FontWeight.Bold)
1142    }
1143    .width('100%')
1144    .backgroundColor(Color.Gray)
1145  }
1146
1147  aboutToAppear() {
1148    console.error(TEST_TAG + ' aboutToAppear');
1149  }
1150
1151  aboutToDisappear() {
1152    console.error(TEST_TAG + ' aboutToDisappear');
1153  }
1154}
1155
1156@Builder
1157function buildComponent() {
1158  TestComponent()
1159}
1160
1161class MyNodeController extends NodeController {
1162  private rootNode: FrameNode | null = null;
1163  private builderNode: BuilderNode<[]> | null = null;
1164
1165  makeNode(uiContext: UIContext): FrameNode | null {
1166    this.rootNode = new FrameNode(uiContext);
1167    this.builderNode = new BuilderNode(uiContext, { selfIdealSize: { width: 200, height: 100 } });
1168    this.builderNode.build(new WrappedBuilder(buildComponent));
1169
1170    const rootRenderNode = this.rootNode.getRenderNode();
1171    if (rootRenderNode !== null) {
1172      rootRenderNode.size = { width: 200, height: 200 };
1173      rootRenderNode.backgroundColor = 0xff00ff00;
1174      rootRenderNode.appendChild(this.builderNode!.getFrameNode()!.getRenderNode());
1175    }
1176
1177    return this.rootNode;
1178  }
1179
1180  printUniqueId(): void {
1181    if (this.rootNode !== null && this.builderNode !== null) {
1182      console.log(`${TEST_TAG} rootNode's uniqueId: ${this.rootNode.getUniqueId()}`);
1183      const frameNode = this.builderNode.getFrameNode();
1184      if (frameNode) {
1185        console.log(`${TEST_TAG} the uniqueId of builderNode's framenode: ${frameNode.getUniqueId()}`);
1186      } else {
1187        console.log(`${TEST_TAG} builderNode's framenode is undefined`);
1188      }
1189    }
1190  }
1191
1192  disposeFrameNode(): void {
1193    if (this.rootNode !== null && this.builderNode !== null) {
1194      console.log(`${TEST_TAG} disposeFrameNode`);
1195      this.rootNode.removeChild(this.builderNode.getFrameNode());
1196      this.builderNode.dispose();
1197
1198      this.rootNode.dispose();
1199    }
1200  }
1201
1202  removeBuilderNode(): void {
1203    const rootRenderNode = this.rootNode!.getRenderNode();
1204    if (rootRenderNode !== null && this.builderNode !== null && this.builderNode.getFrameNode() !== null) {
1205      rootRenderNode.removeChild(this.builderNode!.getFrameNode()!.getRenderNode());
1206    }
1207  }
1208}
1209
1210@Entry
1211@Component
1212struct Index {
1213  private myNodeController: MyNodeController = new MyNodeController();
1214
1215  build() {
1216    Column({ space: 4 }) {
1217      NodeContainer(this.myNodeController)
1218      Button('FrameNode dispose')
1219        .onClick(() => {
1220          this.myNodeController.printUniqueId();
1221          this.myNodeController.disposeFrameNode();
1222          this.myNodeController.printUniqueId();
1223        })
1224        .width('100%')
1225    }
1226  }
1227}
1228```
1229
1230## Using the Lazy Loading Capability of FrameNode
1231
1232To implement lazy loading for custom nodes, you can use the [NodeAdapter](../reference/apis-arkui/js-apis-arkui-frameNode.md#nodeadapter12) object, the counterpart of **LazyForEach** on ArkTS.
1233
1234> **NOTE**
1235>
1236> Make sure the input parameter is not a negative number. If a negative value is provided, no action will be taken.
1237
1238```ts
1239import { FrameNode, NodeController, NodeAdapter, typeNode } from '@kit.ArkUI';
1240
1241const TEST_TAG: string = "FrameNode";
1242
1243class MyNodeAdapter extends NodeAdapter {
1244  uiContext: UIContext
1245  cachePool: Array<FrameNode> = new Array();
1246  changed: boolean = false
1247  reloadTimes: number = 0;
1248  data: Array<string> = new Array();
1249  hostNode?: FrameNode
1250
1251  constructor(uiContext: UIContext, count: number) {
1252    super();
1253    this.uiContext = uiContext;
1254    this.totalNodeCount = count;
1255    this.loadData();
1256  }
1257
1258  reloadData(count: number): void {
1259    this.reloadTimes++;
1260    NodeAdapter.attachNodeAdapter(this, this.hostNode);
1261    this.totalNodeCount = count;
1262    this.loadData();
1263    this.reloadAllItems();
1264  }
1265
1266  refreshData(): void {
1267    let items = this.getAllAvailableItems()
1268    console.log(TEST_TAG + " get All items:" + items.length);
1269    this.totalNodeCount -= 1;
1270    this.reloadAllItems();
1271  }
1272
1273  detachData(): void {
1274    NodeAdapter.detachNodeAdapter(this.hostNode);
1275    this.reloadTimes = 0;
1276  }
1277
1278  loadData(): void {
1279    for (let i = 0; i < this.totalNodeCount; i++) {
1280      this.data[i] = "Adapter ListItem " + i + " r:" + this.reloadTimes;
1281    }
1282  }
1283
1284  changeData(from: number, count: number): void {
1285    this.changed = !this.changed;
1286    for (let i = 0; i < count; i++) {
1287      let index = i + from;
1288      this.data[index] = "Adapter ListItem " + (this.changed ? "changed:" : "") + index + " r:" + this.reloadTimes;
1289    }
1290    this.reloadItem(from, count);
1291  }
1292
1293  insertData(from: number, count: number): void {
1294    for (let i = 0; i < count; i++) {
1295      let index = i + from;
1296      this.data.splice(index, 0, "Adapter ListItem " + from + "-" + i);
1297    }
1298    this.insertItem(from, count);
1299    this.totalNodeCount += count;
1300    console.log(TEST_TAG + " after insert count:" + this.totalNodeCount);
1301  }
1302
1303  removeData(from: number, count: number): void {
1304    let arr = this.data.splice(from, count);
1305    this.removeItem(from, count);
1306    this.totalNodeCount -= arr.length;
1307    console.log(TEST_TAG + " after remove count:" + this.totalNodeCount);
1308  }
1309
1310  moveData(from: number, to: number): void {
1311    let tmp = this.data.splice(from, 1);
1312    this.data.splice(to, 0, tmp[0]);
1313    this.moveItem(from, to);
1314  }
1315
1316  onAttachToNode(target: FrameNode): void {
1317    console.log(TEST_TAG + " onAttachToNode id:" + target.getUniqueId());
1318    this.hostNode = target;
1319  }
1320
1321  onDetachFromNode(): void {
1322    console.log(TEST_TAG + " onDetachFromNode");
1323  }
1324
1325  onGetChildId(index: number): number {
1326    console.log(TEST_TAG + " onGetChildId:" + index);
1327    return index;
1328  }
1329
1330  onCreateChild(index: number): FrameNode {
1331    console.log(TEST_TAG + " onCreateChild:" + index);
1332    if (this.cachePool.length > 0) {
1333      let cacheNode = this.cachePool.pop();
1334      if (cacheNode !== undefined) {
1335        console.log(TEST_TAG + " onCreateChild reused id:" + cacheNode.getUniqueId());
1336        let text = cacheNode?.getFirstChild();
1337        let textNode = text as typeNode.Text;
1338        textNode?.initialize(this.data[index]).fontSize(20);
1339        return cacheNode;
1340      }
1341    }
1342    console.log(TEST_TAG + " onCreateChild createNew");
1343    let itemNode = typeNode.createNode(this.uiContext, "ListItem");
1344    let textNode = typeNode.createNode(this.uiContext, "Text");
1345    textNode.initialize(this.data[index]).fontSize(20);
1346    itemNode.appendChild(textNode);
1347    return itemNode;
1348  }
1349
1350  onDisposeChild(id: number, node: FrameNode): void {
1351    console.log(TEST_TAG + " onDisposeChild:" + id);
1352    if (this.cachePool.length < 10) {
1353      if (!this.cachePool.includes(node)) {
1354        console.log(TEST_TAG + " caching node id:" + node.getUniqueId());
1355        this.cachePool.push(node);
1356      }
1357    } else {
1358      node.dispose();
1359    }
1360  }
1361
1362  onUpdateChild(id: number, node: FrameNode): void {
1363    let index = id;
1364    let text = node.getFirstChild();
1365    let textNode = text as typeNode.Text;
1366    textNode?.initialize(this.data[index]).fontSize(20);
1367  }
1368}
1369
1370class MyNodeAdapterController extends NodeController {
1371  rootNode: FrameNode | null = null;
1372  nodeAdapter: MyNodeAdapter | null = null;
1373
1374  makeNode(uiContext: UIContext): FrameNode | null {
1375    this.rootNode = new FrameNode(uiContext);
1376    let listNode = typeNode.createNode(uiContext, "List");
1377    listNode.initialize({ space: 3 }).borderWidth(2).borderColor(Color.Black);
1378    this.rootNode.appendChild(listNode);
1379    this.nodeAdapter = new MyNodeAdapter(uiContext, 100);
1380    NodeAdapter.attachNodeAdapter(this.nodeAdapter, listNode);
1381    return this.rootNode;
1382  }
1383}
1384
1385@Entry
1386@Component
1387struct ListNodeTest {
1388  adapterController: MyNodeAdapterController = new MyNodeAdapterController();
1389
1390  build() {
1391    Column() {
1392      Text("ListNode Adapter");
1393      NodeContainer(this.adapterController)
1394        .width(300).height(300)
1395        .borderWidth(1).borderColor(Color.Black);
1396      Row() {
1397        Button("Reload")
1398          .onClick(() => {
1399            this.adapterController.nodeAdapter?.reloadData(50);
1400          })
1401        Button("Change")
1402          .onClick(() => {
1403            this.adapterController.nodeAdapter?.changeData(5, 10)
1404          })
1405        Button("Insert")
1406          .onClick(() => {
1407            this.adapterController.nodeAdapter?.insertData(10, 10);
1408          })
1409      }
1410
1411      Row() {
1412        Button("Remove")
1413          .onClick(() => {
1414            this.adapterController.nodeAdapter?.removeData(10, 10);
1415          })
1416        Button("Move")
1417          .onClick(() => {
1418            this.adapterController.nodeAdapter?.moveData(2, 5);
1419          })
1420        Button("Refresh")
1421          .onClick(() => {
1422            this.adapterController.nodeAdapter?.refreshData();
1423          })
1424        Button("Detach")
1425          .onClick(() => {
1426            this.adapterController.nodeAdapter?.detachData();
1427          })
1428      }
1429    }.borderWidth(1)
1430    .width("100%")
1431  }
1432}
1433```
1434