1# 自定义组件节点 (FrameNode)
2
3## 概述
4
5对于拥有自定义前端的第三方框架(如JSON、XML、DOM树等),需将特定的DSL转换为ArkUI的声明式描述。如下图描述了JSON定义的前端框架和ArkUI声明式描述的对应关系。
6
7![zh-cn_image_frame-node01](figures/frame-node01.png)
8
9上述转换过程需要依赖额外的数据驱动,绑定至[Builder](../quick-start/arkts-builder.md)中,较为复杂且性能欠佳。这类框架通常依赖于ArkUI的布局、事件处理、基础的节点操作和自定义能力。大部分组件通过自定义实现,但需结合使用部分系统组件以实现混合显示,如下图示例既使用了FrameNode的自定义方法进行绘制,又使用了系统组件Column及其子组件Text,通过BuilderNode的方式将其挂载到根节点的FrameNode上混合显示。
10
11![zh-cn_image_frame-node02](figures/frame-node02.png)
12
13[FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md)的设计初衷正是为了解决上述转换问题。FrameNode表示组件树中的实体节点,与自定义占位容器组件[NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md)相配合,实现在占位容器内构建一棵自定义的节点树。该节点树支持动态操作,如节点的增加、修改和删除。基础的FrameNode具备设置通用属性和事件回调的功能,同时提供完整的自定义能力,涵盖自定义测量、布局和绘制等方面。
14
15除此之外,ArkUI还提供了获取和遍历系统组件对应代理FrameNode对象的能力(下文简称代理节点)。代理节点能够用于遍历整个UI的树形结构,支持获取系统组件节点的详细信息,以及额外注册组件的事件监听回调。
16
17## 创建和删除节点
18
19FrameNode提供了节点创建和删除的能力。可以通过FrameNode的构造函数创建自定义FrameNode节点,通过构造函数创建的节点对应一个实体的节点。同时,可以通过FrameNode中的[dispose](../reference/apis-arkui/js-apis-arkui-frameNode.md#dispose12)接口来实现与实体节点的绑定关系的解除。
20
21> **说明:**
22>
23> - 在创建FrameNode对象的时候需要传入必选参数UIContext,若未传入UIContext对象或者传入不合法,则节点创建抛出异常。
24>
25> - 自定义占位组件将节点进行显示的时候需要保证UI上下文一致,否则会出现显示异常。
26>
27> - 若不持有FrameNode对象,则该对象会在GC的时候被回收。
28
29## 判断节点是否可修改
30
31[isModifiable](../reference/apis-arkui/js-apis-arkui-frameNode.md#ismodifiable12)用于查询当前节点类型是否为系统组件的代理节点。当FrameNode节点作为系统组件的代理节点的时候,该节点不可修改。即无法修改代理节点的自身属性以及其子节点的结构。
32
33## 获取对应的RenderNode节点
34
35FrameNode提供了[getRenderNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#getrendernode)接口,用于获取FrameNode中的RenderNode。可以通过对获取到的RenderNode对象进行操作,动态修改FrameNode上绘制相关的属性,具体可修改的属性参考[RenderNode](arkts-user-defined-arktsNode-renderNode.md)的接口。
36
37> **说明:**
38>
39> - 无法获取系统组件代理FrameNode的RenderNode对象。
40>
41> - BuilderNode中调用[getFrameNode](../reference/apis-arkui/js-apis-arkui-builderNode.md#getframenode)获取得到的FrameNode节点对象中,可以通过getRenderNode获取对应的根节点的RenderNode对象。
42
43## 操作节点树
44
45FrameNode提供了节点的增、删、查、改的能力,能够修改非代理节点的子树结构。可以对所有FrameNode的节点的父子节点做出查询操作,并返回查询结果。
46
47> **说明:**
48>
49> 对节点进行增、删、改操作的时候,会对非法操作抛出异常信息。
50>
51> 通过查询获得的原生组件的代理节点,仅具备查询节点信息的作用,不具备修改节点属性的功能。代理节点不持有组件的实体节点,即不影响对应的节点的生命周期。
52>
53> 查询节点仅查询获得UI相关的节点,不返回语法节点。
54>
55> 使用自定义组件的场景下,可能查询获得自定义组件的新增节点,节点类型为“\_\_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 + " : " + (err as BusinessError).message);
140      }
141      try {
142        frameNode?.insertChildAfter(frameNode2, null);
143        console.log(TEST_TAG + " insertChildAfter success ");
144      } catch (err) {
145        console.log(TEST_TAG + " insertChildAfter fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message);
146      }
147      setTimeout(() => {
148        try {
149          frameNode?.removeChild(frameNode?.getChild(0))
150          console.log(TEST_TAG + " removeChild success ");
151        } catch (err) {
152          console.log(TEST_TAG + " removeChild fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message);
153        }
154      }, 2000)
155      setTimeout(() => {
156        try {
157          frameNode?.clearChildren();
158          console.log(TEST_TAG + " clearChildren success ");
159        } catch (err) {
160          console.log(TEST_TAG + " clearChildren fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message);
161        }
162      }, 4000)
163    }
164  }
165
166  testInterfaceAboutSearch(frameNode: FrameNode | undefined | null): string {
167    let result: string = "";
168    if (frameNode) {
169      result = result + `current node is ${frameNode.getNodeType()} \n`;
170      result = result + `parent node is ${frameNode.getParent()?.getNodeType()} \n`;
171      result = result + `child count is ${frameNode.getChildrenCount()} \n`;
172      result = result + `first child node is ${frameNode.getFirstChild()?.getNodeType()} \n`;
173      result = result + `second child node is ${frameNode.getChild(1)?.getNodeType()} \n`;
174      result = result + `previousSibling node is ${frameNode.getPreviousSibling()?.getNodeType()} \n`;
175      result = result + `nextSibling node is ${frameNode.getNextSibling()?.getNodeType()} \n`;
176    }
177    return result;
178  }
179
180  checkAppendChild(parent: FrameNode | undefined | null, child: FrameNode | undefined | null) {
181    try {
182      if (parent && child) {
183        parent.appendChild(child);
184        console.log(TEST_TAG + " appendChild success ");
185      }
186    } catch (err) {
187      console.log(TEST_TAG + " appendChild fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message);
188    }
189  }
190}
191
192@Entry
193@Component
194struct Index {
195  @State index: number = 0;
196  @State result: string = ""
197  private myNodeController: MyNodeController = new MyNodeController();
198
199  build() {
200    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
201      List({ space: 20, initialIndex: 0 }) {
202        ListItem() {
203          Column({ space: 5 }) {
204            Text("验证FrameNode子节点的增、删、改功能")
205            Button("对自定义FrameNode进行操作")
206              .fontSize(16)
207              .width(400)
208              .onClick(() => {
209                // 对FrameNode节点进行增、删、改操作,正常实现。
210                this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.frameNode);
211              })
212            Button("对BuilderNode中的代理节点进行操作")
213              .fontSize(16)
214              .width(400)
215              .onClick(() => {
216                // 对BuilderNode代理节点进行增、删、改操作,捕获异常信息。
217                this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.buttonNode?.getFrameNode());
218              })
219            Button("对原生组件中的代理节点进行操作")
220              .fontSize(16)
221              .width(400)
222              .onClick(() => {
223                // 对代理节点进行增、删、改操作,捕获异常信息。
224                this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.rootNode?.getParent());
225              })
226          }
227        }
228
229        ListItem() {
230          Column({ space: 5 }) {
231            Text("验证FrameNode添加子节点的特殊场景")
232            Button("新增BuilderNode的代理节点")
233              .fontSize(16)
234              .width(400)
235              .onClick(() => {
236                let buttonNode = new BuilderNode<[Params]>(this.getUIContext());
237                buttonNode.build(wrapBuilder<[Params]>(buttonBuilder), { text: "BUTTON" })
238                this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, buttonNode?.getFrameNode());
239              })
240            Button("新增原生组件代理节点")
241              .fontSize(16)
242              .width(400)
243              .onClick(() => {
244                this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, this.myNodeController?.rootNode?.getParent());
245              })
246            Button("新增已有父节点的自定义节点")
247              .fontSize(16)
248              .width(400)
249              .onClick(() => {
250                this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, this.myNodeController?.rootNode);
251              })
252          }
253        }
254
255        ListItem() {
256          Column({ space: 5 }) {
257            Text("验证FrameNode节点的查询功能")
258            Button("对自定义FrameNode进行操作")
259              .fontSize(16)
260              .width(400)
261              .onClick(() => {
262                // 对FrameNode节点进行进行查询。当前节点为NodeContainer的子节点。
263                this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode);
264                setTimeout(() => {
265                  // 对FrameNode节点进行进行查询。rootNode下的第一个子节点。
266                  this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.frameNode);
267                }, 2000)
268              })
269            Button("对BuilderNode中的代理节点进行操作")
270              .fontSize(16)
271              .width(400)
272              .onClick(() => {
273                // 对BuilderNode代理节点进行进行查询。当前节点为BuilderNode中的Column节点。
274                this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.buttonNode?.getFrameNode());
275              })
276            Button("对原生组件中的代理节点进行操作")
277              .fontSize(16)
278              .width(400)
279              .onClick(() => {
280                // 对代理节点进行查询。当前节点为NodeContainer。
281                this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode?.getParent());
282              })
283          }
284        }
285      }.height("50%")
286
287      Text(`Result:\n${this.result}`)
288        .fontSize(16)
289        .width(400)
290        .height(200)
291        .padding(30)
292        .borderWidth(1)
293      Column() {
294        Text("This is a NodeContainer.")
295          .textAlign(TextAlign.Center)
296          .borderRadius(10)
297          .backgroundColor(0xFFFFFF)
298          .width('100%')
299          .fontSize(16)
300        NodeContainer(this.myNodeController)
301          .borderWidth(1)
302          .width(400)
303          .height(150)
304      }
305    }
306    .padding({ left: 35, right: 35, top: 35, bottom: 35 })
307    .width("100%")
308    .height("100%")
309  }
310}
311```
312
313## 设置节点通用属性和事件回调
314
315FrameNode提供了[commonAttribute](../reference/apis-arkui/js-apis-arkui-frameNode.md#commonattribute12)和[commonEvent](../reference/apis-arkui/js-apis-arkui-frameNode.md#commonevent12)两个对象用于对设置节点的[通用属性](../reference/apis-arkui/arkui-ts/ts-universal-attributes-size.md)和[设置事件回调](../reference/apis-arkui/arkui-ts/ts-uicommonevent.md)。
316
317> **说明:**
318>
319> - 由于代理节点的属性不可修改,因此通过代理节点的commonAttribute修改节点的基础属性不生效。
320>
321> - 设置的基础事件与原生组件定义的事件平行,参与事件竞争。设置的基础事件不覆盖原生组件事件。同时设置两个事件回调的时候,优先回调原生组件事件。
322
323```ts
324import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI'
325
326class Params {
327  text: string = "this is a text"
328}
329
330@Builder
331function buttonBuilder(params: Params) {
332  Button(params.text)
333    .fontSize(12)
334    .borderRadius(8)
335    .borderWidth(2)
336    .backgroundColor(Color.Orange)
337    .onClick((event: ClickEvent) => {
338      console.log(`Button ${JSON.stringify(event)}`);
339    })
340}
341
342class MyNodeController extends NodeController {
343  public buttonNode: BuilderNode<[Params]> | null = null;
344  public frameNode: FrameNode | null = null;
345  public rootNode: FrameNode | null = null;
346  private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(buttonBuilder);
347
348  makeNode(uiContext: UIContext): FrameNode | null {
349    if (this.rootNode == null) {
350      this.rootNode = new FrameNode(uiContext);
351      // 对rootNode进行属性修改,该节点为自定义的FrameNode节点,修改生效
352      this.rootNode.commonAttribute
353        .width("100%")
354        .height(100)
355        .borderWidth(1)
356        .backgroundColor(Color.Gray)
357    }
358
359    if (this.frameNode == null) {
360      this.frameNode = new FrameNode(uiContext);
361      // 对frameNode进行属性修改,该节点为自定义的FrameNode节点,修改生效
362      this.frameNode.commonAttribute
363        .width("50%")
364        .height(50)
365        .borderWidth(1)
366        .backgroundColor(Color.Pink);
367      this.rootNode.appendChild(this.frameNode);
368    }
369    if (this.buttonNode == null) {
370      this.buttonNode = new BuilderNode<[Params]>(uiContext);
371      this.buttonNode.build(this.wrapBuilder, { text: "This is a Button" })
372      // 对BuilderNode中获取的FrameNode进行属性修改,该节点非自定义的FrameNode节点,修改不生效
373      this.buttonNode?.getFrameNode()?.commonAttribute.position({ x: 100, y: 100 })
374      this.rootNode.appendChild(this.buttonNode.getFrameNode())
375    }
376    return this.rootNode;
377  }
378
379  modifyNode(frameNode: FrameNode | null | undefined, sizeValue: SizeOptions, positionValue: Position) {
380    if (frameNode) {
381      frameNode.commonAttribute.size(sizeValue).position(positionValue);
382    }
383  }
384
385  addClickEvent(frameNode: FrameNode | null | undefined) {
386    if (frameNode) {
387      frameNode.commonEvent.setOnClick((event: ClickEvent) => {
388        console.log(`FrameNode ${JSON.stringify(event)}`);
389      })
390    }
391  }
392}
393
394@Entry
395@Component
396struct Index {
397  private myNodeController: MyNodeController = new MyNodeController();
398
399  build() {
400    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
401      Column({ space: 10 }) {
402        Text("修改节点通用属性-宽高")
403        Button("modify ArkTS-FrameNode")
404          .onClick(() => {
405            // 获取到的是当前页面中的开发者创建的FrameNode对象,该节点可修改。即节点大小与位置。
406            console.log("Check the weather the node can be modified " + this.myNodeController?.frameNode
407            ?.isModifiable());
408            this.myNodeController.modifyNode(this.myNodeController?.frameNode, { width: 150, height: 100 }, {
409              x: 100,
410              y: 0
411            })
412          })
413        Button("modify FrameNode get by BuilderNode")
414          .onClick(() => {
415            // 获取到的是当前页面中的BuilderNode的根节点,该节点不可修改。即节点大小与位置未发生改变。
416            console.log("Check the weather the node can be modified " + this.myNodeController?.buttonNode?.getFrameNode()
417            ?.isModifiable());
418            this.myNodeController.modifyNode(this.myNodeController?.buttonNode?.getFrameNode(), {
419              width: 100,
420              height: 100
421            }, { x: 50, y: 50 })
422          })
423        Button("modify proxyFrameNode get by search")
424          .onClick(() => {
425            // rootNode调用getParent()获取到的是当前页面中的NodeContainer节点,该节点不可修改。即节点大小与位置未发生改变。
426            console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent()
427            ?.isModifiable());
428            this.myNodeController.modifyNode(this.myNodeController?.rootNode?.getParent(), {
429              width: 500,
430              height: 500
431            }, {
432              x: 0,
433              y: 0
434            })
435          })
436      }.padding({ left: 35, right: 35, top: 35, bottom: 35 })
437
438      Column({ space: 10 }) {
439        Text("修改节点点击事件")
440        Button("add click event to ArkTS-FrameNode")
441          .onClick(() => {
442            // 获取到的是当前页面中的开发者创建的FrameNode对象,该节点可增加点击事件。
443            // 增加的点击事件参与事件竞争,即点击事件会在该节点被消费且不不再向父组件冒泡。
444            console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent()
445            ?.isModifiable());
446            this.myNodeController.addClickEvent(this.myNodeController?.frameNode)
447          })
448        Button("add click event to FrameNode get by BuilderNode")
449          .onClick(() => {
450            // 获取到的是当前页面中的BuilderNode的根节点,该类节点可增加点击事件。
451            // 点击的时候优先回调通过原生组件接口设置的click事件回调,然后回调通过commonEvent增加的click监听。
452            console.log("Check the weather the node can be modified " + this.myNodeController?.buttonNode?.getFrameNode()
453            ?.isModifiable());
454            this.myNodeController.addClickEvent(this.myNodeController?.buttonNode?.getFrameNode())
455          })
456        Button("add click event to proxyFrameNode get by search")
457          .onClick(() => {
458            // rootNode调用getParent()获取到的是当前页面中的NodeContainer节点,该类节点可增加点击事件。
459            console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent()
460            ?.isModifiable());
461            this.myNodeController.addClickEvent(this.myNodeController?.rootNode?.getParent());
462          })
463      }.padding({ left: 35, right: 35, top: 35, bottom: 35 })
464
465      NodeContainer(this.myNodeController)
466        .borderWidth(1)
467        .width("100%")
468        .height(100)
469        .onClick((event: ClickEvent) => {
470          console.log(`NodeContainer ${JSON.stringify(event)}`);
471        })
472    }
473    .padding({ left: 35, right: 35, top: 35, bottom: 35 })
474    .width("100%")
475    .height("100%")
476  }
477}
478```
479
480## 自定义测量布局与绘制
481
482通过重写[onDraw](../reference/apis-arkui/js-apis-arkui-frameNode.md#ondraw12)方法,可以自定义FrameNode的绘制内容。[invalidate](../reference/apis-arkui/js-apis-arkui-frameNode.md#invalidate12)接口可以主动触发节点的重新绘制。
483
484通过重写[onMeasure](../reference/apis-arkui/js-apis-arkui-frameNode.md#onmeasure12)可以自定义FrameNode的测量方式,使用[measure](../reference/apis-arkui/js-apis-arkui-frameNode.md#measure12)可以主动传递布局约束触发重新测量。
485
486通过重写[onLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#onlayout12)方法可以自定义FrameNode的布局方式,使用[layout](../reference/apis-arkui/js-apis-arkui-frameNode.md#layout12)方法可以主动传递位置信息并触发重新布局。
487
488[setNeedsLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#setneedslayout12)可以将当前节点标记,在下一帧触发重新布局。
489
490> **说明:**
491>
492> - 对节点进行dispose解引用后,由于FrameNode对象不再对应一个实体节点,invalidate无法触发原有绑定节点的刷新。
493>
494> - 通过onDraw方法进行的自定义绘制,绘制内容大小无法超出组件大小。
495
496```ts
497import { DrawContext, FrameNode, NodeController, Position, Size, UIContext, LayoutConstraint } from '@kit.ArkUI';
498import { drawing } from '@kit.ArkGraphics2D';
499
500function GetChildLayoutConstraint(constraint: LayoutConstraint, child: FrameNode): LayoutConstraint {
501  const size = child.getUserConfigSize();
502  const width = Math.max(
503    Math.min(constraint.maxSize.width, size.width.value),
504    constraint.minSize.width
505  );
506  const height = Math.max(
507    Math.min(constraint.maxSize.height, size.height.value),
508    constraint.minSize.height
509  );
510  const finalSize: Size = { width, height };
511  const res: LayoutConstraint = {
512    maxSize: finalSize,
513    minSize: finalSize,
514    percentReference: finalSize
515  };
516
517  return res;
518}
519
520class MyFrameNode extends FrameNode {
521  public width: number = 100;
522  public offsetY: number = 0;
523  private space: number = 1;
524
525  onMeasure(constraint: LayoutConstraint): void {
526    let sizeRes: Size = { width: vp2px(100), height: vp2px(100) };
527    for (let i = 0;i < this.getChildrenCount(); i++) {
528      let child = this.getChild(i);
529      if (child) {
530        let childConstraint = GetChildLayoutConstraint(constraint, child);
531        child.measure(childConstraint);
532        let size = child.getMeasuredSize();
533        sizeRes.height += size.height + this.space;
534        sizeRes.width = Math.max(sizeRes.width, size.width);
535      }
536    }
537    this.setMeasuredSize(sizeRes);
538  }
539
540  onLayout(position: Position): void {
541    for (let i = 0;i < this.getChildrenCount(); i++) {
542      let child = this.getChild(i);
543      if (child) {
544        child.layout({
545          x: vp2px(100),
546          y: vp2px(this.offsetY)
547        });
548        let layoutPosition = child.getLayoutPosition();
549        console.log("child position:" + JSON.stringify(layoutPosition));
550      }
551    }
552    this.setLayoutPosition(position);
553  }
554
555  onDraw(context: DrawContext) {
556    const canvas = context.canvas;
557    const pen = new drawing.Pen();
558    pen.setStrokeWidth(15);
559    pen.setColor({ alpha: 255, red: 255, green: 0, blue: 0 });
560    canvas.attachPen(pen);
561    canvas.drawRect({
562      left: 50,
563      right: this.width + 50,
564      top: 50,
565      bottom: this.width + 50,
566    });
567    canvas.detachPen();
568  }
569
570  addWidth() {
571    this.width = (this.width + 10) % 50 + 100;
572  }
573}
574
575class MyNodeController extends NodeController {
576  public rootNode: MyFrameNode | null = null;
577
578  makeNode(context: UIContext): FrameNode | null {
579    this.rootNode = new MyFrameNode(context);
580    this.rootNode?.commonAttribute?.size({ width: 100, height: 100 }).backgroundColor(Color.Green);
581    let frameNode: FrameNode = new FrameNode(context);
582    this.rootNode.appendChild(frameNode);
583    frameNode.commonAttribute.width(10).height(10).backgroundColor(Color.Pink);
584    return this.rootNode;
585  }
586}
587
588@Entry
589@Component
590struct Index {
591  private nodeController: MyNodeController = new MyNodeController();
592
593  build() {
594    Row() {
595      Column() {
596        NodeContainer(this.nodeController)
597          .width('100%')
598          .height(200)
599          .backgroundColor('#FFF0F0F0')
600        Button('Invalidate')
601          .margin(10)
602          .onClick(() => {
603            this.nodeController?.rootNode?.addWidth();
604            this.nodeController?.rootNode?.invalidate();
605          })
606        Button('UpdateLayout')
607          .onClick(() => {
608            let node = this.nodeController.rootNode;
609            node!.offsetY = (node!.offsetY + 10) % 110;
610            this.nodeController?.rootNode?.setNeedsLayout();
611          })
612      }
613      .width('100%')
614      .height('100%')
615    }
616    .height('100%')
617  }
618}
619```
620
621## 查找节点及获取基础信息
622
623FrameNode提供了查询接口用于返回实体节点的基础信息。具体返回的信息内容参考FrameNode中提供的接口。
624
625查找获得FrameNode的方式包括三种:
626
6271. 使用[getFrameNodeById](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyid12)获取。
628
6292. 使用[getFrameNodeByUniqueId](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyuniqueid12)获取。
630
6313. 通过[无感监听](../reference/apis-arkui/js-apis-arkui-observer.md)获取。
632
633> **说明:**
634>
635> 1、当前接口提供的可查询的信息包括:
636>
637> - 节点大小:[getMeasuredSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getmeasuredsize12),[getUserConfigSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigsize12)
638>
639> - 布局信息:[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)
640>
641> - 节点信息:[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)
642>
643> 2、无法获取UINode类型节点,例如:JsView节点、[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)、if/else组件等。
644
645## 获取节点位置偏移信息
646
647FrameNode提供了查询节点相对窗口、父组件以及屏幕位置偏移的信息接口([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))。
648
649```ts
650import { NodeController, FrameNode, UIContext } from '@kit.ArkUI';
651
652const TEST_TAG : string = "FrameNode"
653class MyNodeController extends NodeController {
654  public frameNode: FrameNode | null = null;
655  private rootNode: FrameNode | null = null;
656  makeNode(uiContext: UIContext): FrameNode | null {
657    this.rootNode = new FrameNode(uiContext);
658    this.frameNode =  new FrameNode(uiContext);
659    this.rootNode.appendChild(this.frameNode);
660    return this.rootNode;
661  }
662  getPositionToWindow()
663  {
664    let positionToWindow = this.rootNode?.getPositionToWindow(); // 获取FrameNode相对于窗口的位置偏移
665    console.log(TEST_TAG + JSON.stringify(positionToWindow));
666  }
667  getPositionToParent()
668  {
669    let positionToParent = this.rootNode?.getPositionToParent(); // 获取FrameNode相对于父组件的位置偏移
670    console.log(TEST_TAG + JSON.stringify(positionToParent));
671  }
672  getPositionToScreen()
673  {
674    let positionToScreen = this.rootNode?.getPositionToScreen(); // 获取FrameNode相对于屏幕的位置偏移
675    console.log(TEST_TAG + JSON.stringify(positionToScreen));
676  }
677  getPositionToWindowWithTransform()
678  {
679    let positionToWindowWithTransform = this.rootNode?.getPositionToWindowWithTransform(); // 获取FrameNode相对于窗口带有绘制属性的位置偏移
680    console.log(TEST_TAG + JSON.stringify(positionToWindowWithTransform));
681  }
682  getPositionToParentWithTransform()
683  {
684    let positionToParentWithTransform = this.rootNode?.getPositionToParentWithTransform(); // 获取FrameNode相对于父组件带有绘制属性的位置偏移
685    console.log(TEST_TAG + JSON.stringify(positionToParentWithTransform));
686  }
687  getPositionToScreenWithTransform()
688  {
689    let positionToScreenWithTransform = this.rootNode?.getPositionToScreenWithTransform(); // 获取FrameNode相对于屏幕带有绘制属性的位置偏移
690    console.log(TEST_TAG + JSON.stringify(positionToScreenWithTransform));
691  }
692}
693
694@Entry
695@Component
696struct Index {
697  private myNodeController: MyNodeController = new MyNodeController();
698  build() {
699    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
700      Button("getPositionToWindow")
701        .width(300)
702        .onClick(()=>{
703          this.myNodeController.getPositionToWindow();
704        })
705      Button("getPositionToParent")
706        .width(300)
707        .onClick(()=>{
708          this.myNodeController.getPositionToParent();
709        })
710      Button("getPositionToScreen")
711        .width(300)
712        .onClick(()=>{
713          this.myNodeController.getPositionToScreen();
714        })
715      Button("getPositionToParentWithTransform")
716        .width(300)
717        .onClick(()=>{
718          this.myNodeController.getPositionToParentWithTransform();
719        })
720      Button("getPositionToWindowWithTransform")
721        .width(300)
722        .onClick(()=>{
723          this.myNodeController.getPositionToWindowWithTransform();
724        })
725      Button("getPositionToScreenWithTransform")
726        .width(300)
727        .onClick(()=>{
728          this.myNodeController.getPositionToScreenWithTransform();
729        })
730      Column(){
731        Text("This is a NodeContainer.")
732          .textAlign(TextAlign.Center).borderRadius(10).backgroundColor(0xFFFFFF)
733          .width('100%').fontSize(16)
734        NodeContainer(this.myNodeController)
735          .borderWidth(1)
736          .width(300)
737          .height(100)
738      }
739    }
740    .padding({ left: 35, right: 35, top: 35, bottom: 35 })
741    .width("100%")
742    .height("100%")
743  }
744}
745```
746
747## 通过typeNode创建具体类型的FrameNode节点
748
749通过TypeNode创建具体类型的FrameNode节点,可以根据属性获取接口来检索用户设置的属性信息。
750
751```ts
752import { NodeController, FrameNode, UIContext, BuilderNode, typeNode } from '@kit.ArkUI';
753import { BusinessError } from '@kit.BasicServicesKit';
754
755class Params {
756  text: string = "";
757
758  constructor(text: string) {
759    this.text = text;
760  }
761}
762
763@Builder
764function buildText(params: Params) {
765  Column() {
766    Text(params.text)
767      .id("buildText")
768      .border({width:1})
769      .padding(1)
770      .fontSize(25)
771      .fontWeight(FontWeight.Bold)
772      .margin({ top: 10 })
773      .visibility(Visibility.Visible)
774      .opacity(0.7)
775      .customProperty("key1", "value1")
776      .width(300)
777  }
778}
779
780const TEST_TAG : string = "FrameNode"
781class MyNodeController extends NodeController {
782  public frameNode: typeNode.Column | null = null;
783  public uiContext: UIContext | undefined = undefined;
784  private rootNode: FrameNode | null = null;
785  private textNode: BuilderNode<[Params]> | null = null;
786  public textTypeNode: typeNode.Text | null = null;
787  private message: string = "DEFAULT";
788
789  makeNode(uiContext: UIContext): FrameNode | null {
790    this.rootNode = new FrameNode(uiContext);
791    this.uiContext = uiContext;
792    this.frameNode = typeNode.createNode(uiContext, "Column");
793    this.frameNode.attribute
794      .width("100%")
795      .height("100%")
796    this.rootNode.appendChild(this.frameNode);
797    this.textNode = new BuilderNode(uiContext);
798    this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message));
799    this.frameNode.appendChild(this.textNode.getFrameNode());
800    this.textTypeNode = typeNode.createNode(uiContext, "Text");
801    this.textTypeNode.initialize("textTypeNode")
802      .fontSize(25)
803      .visibility(Visibility.Visible)
804      .id("textTypeNode")
805    this.frameNode.appendChild(this.textTypeNode);
806    return this.rootNode;
807  }
808  removeChild(frameNode: FrameNode)
809  {
810    let parent = frameNode.getParent();
811    if (parent) {
812      parent.removeChild(frameNode);
813
814    }
815  }
816  getUserConfigBorderWidth(frameNode: FrameNode)
817  {
818    let userConfigBorderWidth = frameNode?.getUserConfigBorderWidth(); // 获取用户设置的边框宽度
819    console.log(TEST_TAG + JSON.stringify(userConfigBorderWidth));
820  }
821  getUserConfigPadding(frameNode: FrameNode)
822  {
823    let userConfigPadding = frameNode?.getUserConfigPadding(); // 获取用户设置的内边距
824    console.log(TEST_TAG + JSON.stringify(userConfigPadding));
825  }
826  getUserConfigMargin(frameNode: FrameNode)
827  {
828    let userConfigMargin = frameNode?.getUserConfigMargin(); // 获取用户设置的外边距
829    console.log(TEST_TAG + JSON.stringify(userConfigMargin));
830  }
831  getUserConfigSize(frameNode: FrameNode)
832  {
833    let userConfigSize = frameNode?.getUserConfigSize(); // 获取用户设置的宽高
834    console.log(TEST_TAG + JSON.stringify(userConfigSize));
835  }
836  getId(frameNode: FrameNode)
837  {
838    let id = frameNode?.getId(); // 获取用户设置的节点ID
839    console.log(TEST_TAG + id);
840  }
841  getUniqueId(frameNode: FrameNode)
842  {
843    let uniqueId = frameNode?.getUniqueId(); // 获取系统分配的唯一标识的节点UniqueID
844    console.log(TEST_TAG + uniqueId);
845  }
846  getNodeType(frameNode: FrameNode)
847  {
848    let nodeType = frameNode?.getNodeType(); // 获取节点的类型
849    console.log(TEST_TAG + nodeType);
850  }
851  getOpacity(frameNode: FrameNode)
852  {
853    let opacity = frameNode?.getOpacity(); // 获取节点的不透明度
854    console.log(TEST_TAG + JSON.stringify(opacity));
855  }
856  isVisible(frameNode: FrameNode)
857  {
858    let visible = frameNode?.isVisible(); // 获取节点是否可见
859    console.log(TEST_TAG + JSON.stringify(visible));
860  }
861  isClipToFrame(frameNode: FrameNode)
862  {
863    let clipToFrame = frameNode?.isClipToFrame(); // 获取节点是否是剪裁到组件区域
864    console.log(TEST_TAG + JSON.stringify(clipToFrame));
865  }
866  isAttached(frameNode: FrameNode)
867  {
868    let attached = frameNode?.isAttached(); // 获取节点是否被挂载到主节点树上
869    console.log(TEST_TAG + JSON.stringify(attached));
870  }
871  getInspectorInfo(frameNode: FrameNode)
872  {
873    let inspectorInfo = frameNode?.getInspectorInfo(); // 获取节点的结构信息
874    console.log(TEST_TAG + JSON.stringify(inspectorInfo));
875  }
876}
877
878@Entry
879@Component
880struct Index {
881  private myNodeController: MyNodeController = new MyNodeController();
882  @State index : number = 0;
883  build() {
884    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
885      Column(){
886        Text("This is a NodeContainer.")
887          .textAlign(TextAlign.Center).borderRadius(10).backgroundColor(0xFFFFFF)
888          .width('100%').fontSize(16)
889        NodeContainer(this.myNodeController)
890          .borderWidth(1)
891          .width(300)
892          .height(100)
893      }
894      Button("getUserConfigBorderWidth")
895        .width(300)
896        .onClick(()=>{
897          const uiContext: UIContext = this.getUIContext();
898          if (uiContext) {
899            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
900            if (node) {
901              this.myNodeController.getUserConfigBorderWidth(node);
902            }
903          }
904        })
905      Button("getUserConfigPadding")
906        .width(300)
907        .onClick(()=>{
908          const uiContext: UIContext = this.getUIContext();
909          if (uiContext) {
910            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
911            if (node) {
912              this.myNodeController.getUserConfigPadding(node);
913            }
914          }
915        })
916      Button("getUserConfigMargin")
917        .width(300)
918        .onClick(()=>{
919          const uiContext: UIContext = this.getUIContext();
920          if (uiContext) {
921            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
922            if (node) {
923              this.myNodeController.getUserConfigMargin(node);
924            }
925          }
926        })
927      Button("getUserConfigSize")
928        .width(300)
929        .onClick(()=>{
930          const uiContext: UIContext = this.getUIContext();
931          if (uiContext) {
932            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
933            if (node) {
934              this.myNodeController.getUserConfigSize(node);
935            }
936          }
937        })
938      Button("getId")
939        .width(300)
940        .onClick(()=>{
941          const uiContext: UIContext = this.getUIContext();
942          if (uiContext) {
943            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
944            if (node) {
945              this.myNodeController.getId(node);
946            }
947          }
948        })
949      Button("getUniqueId")
950        .width(300)
951        .onClick(()=>{
952          const uiContext: UIContext = this.getUIContext();
953          if (uiContext) {
954            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
955            if (node) {
956              this.myNodeController.getUniqueId(node);
957            }
958          }
959        })
960      Button("getNodeType")
961        .width(300)
962        .onClick(()=>{
963          const uiContext: UIContext = this.getUIContext();
964          if (uiContext) {
965            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
966            if (node) {
967              this.myNodeController.getNodeType(node);
968            }
969          }
970        })
971      Button("getOpacity")
972        .width(300)
973        .onClick(()=>{
974          const uiContext: UIContext = this.getUIContext();
975          if (uiContext) {
976            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
977            if (node) {
978              this.myNodeController.getOpacity(node);
979            }
980          }
981        })
982      Button("isVisible")
983        .width(300)
984        .onClick(()=>{
985          const uiContext: UIContext = this.getUIContext();
986          if (uiContext) {
987            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
988            if (node) {
989              this.myNodeController.isVisible(node);
990            }
991          }
992        })
993      Button("isClipToFrame")
994        .width(300)
995        .onClick(()=>{
996          const uiContext: UIContext = this.getUIContext();
997          if (uiContext) {
998            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
999            if (node) {
1000              this.myNodeController.isClipToFrame(node);
1001            }
1002          }
1003        })
1004      Button("isAttached")
1005        .width(300)
1006        .onClick(()=>{
1007          const uiContext: UIContext = this.getUIContext();
1008          if (uiContext) {
1009            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
1010            if (node) {
1011              this.myNodeController.isAttached(node);
1012            }
1013          }
1014        })
1015      Button("remove Text")
1016        .width(300)
1017        .onClick(()=>{
1018          const uiContext: UIContext = this.getUIContext();
1019          if (uiContext) {
1020            const node: FrameNode | null = uiContext.getFrameNodeById("textTypeNode") || null;
1021            if (node) {
1022              this.myNodeController.removeChild(node);
1023              this.myNodeController.isAttached(node);
1024            }
1025          }
1026        })
1027      Button("getInspectorInfo")
1028        .width(300)
1029        .onClick(()=>{
1030          const uiContext: UIContext = this.getUIContext();
1031          if (uiContext) {
1032            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
1033            if (node) {
1034              this.myNodeController.getInspectorInfo(node);
1035            }
1036          }
1037        })
1038      Button("getCustomProperty")
1039        .width(300)
1040        .onClick(()=>{
1041          const uiContext: UIContext = this.getUIContext();
1042          if (uiContext) {
1043            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
1044            if (node) {
1045              const property = node.getCustomProperty("key1");
1046              console.log(TEST_TAG, JSON.stringify(property));
1047            }
1048          }
1049        })
1050    }
1051    .padding({ left: 35, right: 35, top: 35, bottom: 35 })
1052    .width("100%")
1053    .height("100%")
1054  }
1055}
1056```
1057
1058## 解除当前FrameNode对象对实体FrameNode节点的引用关系
1059
1060使用[dispose](../reference/apis-arkui/js-apis-arkui-frameNode.md#dispose12)接口可以立即解除当前FrameNode对象对实体FrameNode节点的引用关系。
1061
1062> **说明:**
1063>
1064> 在调用dispose方法后,FrameNode对象不再对应任何实际的FrameNode节点。此时,若尝试调用以下查询接口:getMeasuredSize、getLayoutPosition、getUserConfigBorderWidth、getUserConfigPadding、getUserConfigMargin、getUserConfigSize,将导致应用程序触发jscrash。
1065>
1066> 通过[getUniqueId](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuniqueid12)可以判断当前FrameNode是否对应一个实体FrameNode节点。当UniqueId大于0时表示该对象对应一个实体FrameNode节点。
1067
1068```ts
1069import { NodeController, FrameNode, BuilderNode } from '@kit.ArkUI';
1070
1071@Component
1072struct TestComponent {
1073  build() {
1074    Column() {
1075      Text('This is a BuilderNode.')
1076        .fontSize(16)
1077        .fontWeight(FontWeight.Bold)
1078    }
1079    .width('100%')
1080    .backgroundColor(Color.Gray)
1081  }
1082
1083  aboutToAppear() {
1084    console.error('aboutToAppear');
1085  }
1086
1087  aboutToDisappear() {
1088    console.error('aboutToDisappear');
1089  }
1090}
1091
1092@Builder
1093function buildComponent() {
1094  TestComponent()
1095}
1096
1097class MyNodeController extends NodeController {
1098  private rootNode: FrameNode | null = null;
1099  private builderNode: BuilderNode<[]> | null = null;
1100
1101  makeNode(uiContext: UIContext): FrameNode | null {
1102    this.rootNode = new FrameNode(uiContext);
1103    this.builderNode = new BuilderNode(uiContext, { selfIdealSize: { width: 200, height: 100 } });
1104    this.builderNode.build(new WrappedBuilder(buildComponent));
1105
1106    const rootRenderNode = this.rootNode.getRenderNode();
1107    if (rootRenderNode !== null) {
1108      rootRenderNode.size = { width: 200, height: 200 };
1109      rootRenderNode.backgroundColor = 0xff00ff00;
1110      rootRenderNode.appendChild(this.builderNode!.getFrameNode()!.getRenderNode());
1111    }
1112
1113    return this.rootNode;
1114  }
1115
1116  disposeFrameNode() {
1117    if (this.rootNode !== null && this.builderNode !== null) {
1118      this.rootNode.removeChild(this.builderNode.getFrameNode());
1119      this.builderNode.dispose();
1120
1121      this.rootNode.dispose();
1122    }
1123  }
1124
1125  removeBuilderNode() {
1126    const rootRenderNode = this.rootNode!.getRenderNode();
1127    if (rootRenderNode !== null && this.builderNode !== null && this.builderNode.getFrameNode() !== null) {
1128      rootRenderNode.removeChild(this.builderNode!.getFrameNode()!.getRenderNode());
1129    }
1130  }
1131}
1132
1133@Entry
1134@Component
1135struct Index {
1136  private myNodeController: MyNodeController = new MyNodeController();
1137
1138  build() {
1139    Column({ space: 4 }) {
1140      NodeContainer(this.myNodeController)
1141      Button('FrameNode dispose')
1142        .onClick(() => {
1143          this.myNodeController.disposeFrameNode();
1144        })
1145        .width('100%')
1146    }
1147  }
1148}
1149```
1150
1151## FrameNode的数据懒加载能力
1152
1153提供[NodeAdapter](../reference/apis-arkui/js-apis-arkui-frameNode.md#nodeadapter12)对象替代ArkTS侧的LazyForEach功能,提供自定义节点的数据懒加载功能,实现按需迭代数据。
1154
1155> **说明:**
1156>
1157> 入参不能为负数,入参为负数时不做处理。
1158
1159```ts
1160import { FrameNode, NodeController, NodeAdapter, typeNode } from '@kit.ArkUI';
1161
1162class MyNodeAdapter extends NodeAdapter {
1163  uiContext: UIContext
1164  cachePool: Array<FrameNode> = new Array();
1165  changed: boolean = false
1166  reloadTimes: number = 0;
1167  data: Array<string> = new Array();
1168  hostNode?: FrameNode
1169
1170  constructor(uiContext: UIContext, count: number) {
1171    super();
1172    this.uiContext = uiContext;
1173    this.totalNodeCount = count;
1174    this.loadData();
1175  }
1176
1177  reloadData(count: number): void {
1178    this.reloadTimes++;
1179    NodeAdapter.attachNodeAdapter(this, this.hostNode);
1180    this.totalNodeCount = count;
1181    this.loadData();
1182    this.reloadAllItems();
1183  }
1184
1185  refreshData(): void {
1186    let items = this.getAllAvailableItems()
1187    console.log("UINodeAdapter get All items:" + items.length);
1188    this.reloadAllItems();
1189  }
1190
1191  detachData(): void {
1192    NodeAdapter.detachNodeAdapter(this.hostNode);
1193    this.reloadTimes = 0;
1194  }
1195
1196  loadData(): void {
1197    for (let i = 0; i < this.totalNodeCount; i++) {
1198      this.data[i] = "Adapter ListItem " + i + " r:" + this.reloadTimes;
1199    }
1200  }
1201
1202  changeData(from: number, count: number): void {
1203    this.changed = !this.changed;
1204    for (let i = 0; i < count; i++) {
1205      let index = i + from;
1206      this.data[index] = "Adapter ListItem " + (this.changed ? "changed:" : "") + index + " r:" + this.reloadTimes;
1207    }
1208    this.reloadItem(from, count);
1209  }
1210
1211  insertData(from: number, count: number): void {
1212    for (let i = 0; i < count; i++) {
1213      let index = i + from;
1214      this.data.splice(index, 0, "Adapter ListItem " + from + "-" + i);
1215    }
1216    this.insertItem(from, count);
1217    this.totalNodeCount += count;
1218    console.log("UINodeAdapter after insert count:" + this.totalNodeCount);
1219  }
1220
1221  removeData(from: number, count: number): void {
1222    let arr = this.data.splice(from, count);
1223    this.removeItem(from, count);
1224    this.totalNodeCount -= arr.length;
1225    console.log("UINodeAdapter after remove count:" + this.totalNodeCount);
1226  }
1227
1228  moveData(from: number, to: number): void {
1229    let tmp = this.data.splice(from, 1);
1230    this.data.splice(to, 0, tmp[0]);
1231    this.moveItem(from, to);
1232  }
1233
1234  onAttachToNode(target: FrameNode): void {
1235    console.log("UINodeAdapter onAttachToNode id:" + target.getUniqueId());
1236    this.hostNode = target;
1237  }
1238
1239  onDetachFromNode(): void {
1240    console.log("UINodeAdapter onDetachFromNode");
1241  }
1242
1243  onGetChildId(index: number): number {
1244    console.log("UINodeAdapter onGetChildId:" + index);
1245    return index;
1246  }
1247
1248  onCreateChild(index: number): FrameNode {
1249    console.log("UINodeAdapter onCreateChild:" + index);
1250    if (this.cachePool.length > 0) {
1251      let cacheNode = this.cachePool.pop();
1252      if (cacheNode !== undefined) {
1253        console.log("UINodeAdapter onCreateChild reused id:" + cacheNode.getUniqueId());
1254        let text = cacheNode?.getFirstChild();
1255        let textNode = text as typeNode.Text;
1256        textNode?.initialize(this.data[index]).fontSize(20);
1257        return cacheNode;
1258      }
1259    }
1260    console.log("UINodeAdapter onCreateChild createNew");
1261    let itemNode = typeNode.createNode(this.uiContext, "ListItem");
1262    let textNode = typeNode.createNode(this.uiContext, "Text");
1263    textNode.initialize(this.data[index]).fontSize(20);
1264    itemNode.appendChild(textNode);
1265    return itemNode;
1266  }
1267
1268  onDisposeChild(id: number, node: FrameNode): void {
1269    console.log("UINodeAdapter onDisposeChild:" + id);
1270    if (this.cachePool.length < 10) {
1271      if (!this.cachePool.includes(node)) {
1272        console.log("UINodeAdapter caching node id:" + node.getUniqueId());
1273        this.cachePool.push(node);
1274      }
1275    } else {
1276      node.dispose();
1277    }
1278  }
1279
1280  onUpdateChild(id: number, node: FrameNode): void {
1281    let index = id;
1282    let text = node.getFirstChild();
1283    let textNode = text as typeNode.Text;
1284    textNode?.initialize(this.data[index]).fontSize(20);
1285  }
1286}
1287
1288class MyNodeAdapterController extends NodeController {
1289  rootNode: FrameNode | null = null;
1290  nodeAdapter: MyNodeAdapter | null = null;
1291
1292  makeNode(uiContext: UIContext): FrameNode | null {
1293    this.rootNode = new FrameNode(uiContext);
1294    let listNode = typeNode.createNode(uiContext, "List");
1295    listNode.initialize({ space: 3 }).borderWidth(2).borderColor(Color.Black);
1296    this.rootNode.appendChild(listNode);
1297    this.nodeAdapter = new MyNodeAdapter(uiContext, 100);
1298    NodeAdapter.attachNodeAdapter(this.nodeAdapter, listNode);
1299    return this.rootNode;
1300  }
1301}
1302
1303@Entry
1304@Component
1305struct ListNodeTest {
1306  adapterController: MyNodeAdapterController = new MyNodeAdapterController();
1307
1308  build() {
1309    Column() {
1310      Text("ListNode Adapter");
1311      NodeContainer(this.adapterController)
1312        .width(300).height(300)
1313        .borderWidth(1).borderColor(Color.Black);
1314      Row() {
1315        Button("Reload")
1316          .onClick(() => {
1317            this.adapterController.nodeAdapter?.reloadData(50);
1318          })
1319        Button("Change")
1320          .onClick(() => {
1321            this.adapterController.nodeAdapter?.changeData(5, 10)
1322          })
1323        Button("Insert")
1324          .onClick(() => {
1325            this.adapterController.nodeAdapter?.insertData(10, 10);
1326          })
1327      }
1328
1329      Row() {
1330        Button("Remove")
1331          .onClick(() => {
1332            this.adapterController.nodeAdapter?.removeData(10, 10);
1333          })
1334        Button("Move")
1335          .onClick(() => {
1336            this.adapterController.nodeAdapter?.moveData(2, 5);
1337          })
1338        Button("Refresh")
1339          .onClick(() => {
1340            this.adapterController.nodeAdapter?.refreshData();
1341          })
1342        Button("Detach")
1343          .onClick(() => {
1344            this.adapterController.nodeAdapter?.detachData();
1345          })
1346      }
1347    }.borderWidth(1)
1348    .width("100%")
1349  }
1350}
1351```
1352