• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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](../ui/state-management/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 + " : " +
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("验证FrameNode子节点的增、删、改功能")
210            Button("对自定义FrameNode进行操作")
211              .fontSize(16)
212              .width(400)
213              .onClick(() => {
214                // 对FrameNode节点进行增、删、改操作,正常实现。
215                this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.frameNode);
216              })
217            Button("对BuilderNode中的代理节点进行操作")
218              .fontSize(16)
219              .width(400)
220              .onClick(() => {
221                // 对BuilderNode代理节点进行增、删、改操作,捕获异常信息。
222                this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.buttonNode?.getFrameNode());
223              })
224            Button("对系统组件中的代理节点进行操作")
225              .fontSize(16)
226              .width(400)
227              .onClick(() => {
228                // 对代理节点进行增、删、改操作,捕获异常信息。
229                this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.rootNode?.getParent());
230              })
231          }
232        }
233
234        ListItem() {
235          Column({ space: 5 }) {
236            Text("验证FrameNode添加子节点的特殊场景")
237            Button("新增BuilderNode的代理节点")
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("新增系统组件代理节点")
246              .fontSize(16)
247              .width(400)
248              .onClick(() => {
249                this.myNodeController.checkAppendChild(this.myNodeController?.frameNode,
250                  this.myNodeController?.rootNode?.getParent());
251              })
252            Button("新增已有父节点的自定义节点")
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("验证FrameNode节点的查询功能")
265            Button("对自定义FrameNode进行操作")
266              .fontSize(16)
267              .width(400)
268              .onClick(() => {
269                // 对FrameNode节点进行进行查询。当前节点为NodeContainer的子节点。
270                this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode);
271                setTimeout(() => {
272                  // 对FrameNode节点进行进行查询。rootNode下的第一个子节点。
273                  this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.frameNode);
274                }, 2000)
275              })
276            Button("对BuilderNode中的代理节点进行操作")
277              .fontSize(16)
278              .width(400)
279              .onClick(() => {
280                // 对BuilderNode代理节点进行进行查询。当前节点为BuilderNode中的Column节点。
281                this.result =
282                  this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.buttonNode?.getFrameNode());
283              })
284            Button("对系统组件中的代理节点进行操作")
285              .fontSize(16)
286              .width(400)
287              .onClick(() => {
288                // 对代理节点进行查询。当前节点为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## 设置节点通用属性和事件回调
328
329FrameNode提供了[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-component-general-attributes.md)和[设置事件回调](../reference/apis-arkui/arkui-ts/ts-uicommonevent.md)。
330
331> **说明:**
332>
333> - 由于代理节点的属性不可修改,因此通过代理节点的commonAttribute修改节点的基础属性不生效。
334>
335> - 设置的基础事件与系统组件定义的事件平行,参与事件竞争。设置的基础事件不覆盖系统组件事件。同时设置两个事件回调的时候,优先回调系统组件事件。
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      // 对rootNode进行属性修改,该节点为自定义的FrameNode节点,修改生效
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      // 对frameNode进行属性修改,该节点为自定义的FrameNode节点,修改生效
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      // 对BuilderNode中获取的FrameNode进行属性修改,该节点非自定义的FrameNode节点,修改不生效
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("修改节点通用属性-宽高")
417        Button("modify ArkTS-FrameNode")
418          .onClick(() => {
419            // 获取到的是当前页面中的开发者创建的FrameNode对象,该节点可修改。即节点大小与位置。
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            // 获取到的是当前页面中的BuilderNode的根节点,该节点不可修改。即节点大小与位置未发生改变。
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            // rootNode调用getParent()获取到的是当前页面中的NodeContainer节点,该节点不可修改。即节点大小与位置未发生改变。
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("修改节点点击事件")
461        Button("add click event to ArkTS-FrameNode")
462          .onClick(() => {
463            // 获取到的是当前页面中的开发者创建的FrameNode对象,该节点可增加点击事件。
464            // 增加的点击事件参与事件竞争,即点击事件会在该节点被消费且不不再向父组件冒泡。
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            // 获取到的是当前页面中的BuilderNode的根节点,该类节点可增加点击事件。
472            // 点击的时候优先回调通过系统组件接口设置的click事件回调,然后回调通过commonEvent增加的click监听。
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            // rootNode调用getParent()获取到的是当前页面中的NodeContainer节点,该类节点可增加点击事件。
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## 自定义测量布局与绘制
514
515通过重写[onDraw](../reference/apis-arkui/js-apis-arkui-frameNode.md#ondraw12)方法,可以自定义FrameNode的绘制内容。[invalidate](../reference/apis-arkui/js-apis-arkui-frameNode.md#invalidate12)接口可以主动触发节点的重新绘制。
516
517通过重写[onMeasure](../reference/apis-arkui/js-apis-arkui-frameNode.md#onmeasure12)可以自定义FrameNode的测量方式,使用[measure](../reference/apis-arkui/js-apis-arkui-frameNode.md#measure12)可以主动传递布局约束触发重新测量。
518
519通过重写[onLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#onlayout12)方法可以自定义FrameNode的布局方式,使用[layout](../reference/apis-arkui/js-apis-arkui-frameNode.md#layout12)方法可以主动传递位置信息并触发重新布局。
520
521[setNeedsLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#setneedslayout12)可以将当前节点标记,在下一帧触发重新布局。
522
523> **说明:**
524>
525> - 对节点进行dispose解引用后,由于FrameNode对象不再对应一个实体节点,invalidate无法触发原有绑定节点的刷新。
526>
527> - 通过onDraw方法进行的自定义绘制,绘制内容大小无法超出组件大小。
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  uiContext: UIContext;
558
559  constructor(uiContext: UIContext) {
560    super(uiContext);
561    this.uiContext = uiContext;
562  }
563
564  onMeasure(constraint: LayoutConstraint): void {
565    let sizeRes: Size = { width: this.uiContext.vp2px(100), height: this.uiContext.vp2px(100) };
566    for (let i = 0; i < this.getChildrenCount(); i++) {
567      let child = this.getChild(i);
568      if (child) {
569        let childConstraint = GetChildLayoutConstraint(constraint, child);
570        child.measure(childConstraint);
571        let size = child.getMeasuredSize();
572        sizeRes.height += size.height + this.space;
573        sizeRes.width = Math.max(sizeRes.width, size.width);
574      }
575    }
576    this.setMeasuredSize(sizeRes);
577  }
578
579  onLayout(position: Position): void {
580    for (let i = 0; i < this.getChildrenCount(); i++) {
581      let child = this.getChild(i);
582      if (child) {
583        child.layout({
584          x: this.uiContext.vp2px(100),
585          y: this.uiContext.vp2px(this.offsetY)
586        });
587        let layoutPosition = child.getLayoutPosition();
588        console.log("child position:" + JSON.stringify(layoutPosition));
589      }
590    }
591    this.setLayoutPosition(position);
592  }
593
594  onDraw(context: DrawContext) {
595    const canvas = context.canvas;
596    const pen = new drawing.Pen();
597    pen.setStrokeWidth(15);
598    pen.setColor({
599      alpha: 255,
600      red: 255,
601      green: 0,
602      blue: 0
603    });
604    canvas.attachPen(pen);
605    canvas.drawRect({
606      left: 50,
607      right: this.width + 50,
608      top: 50,
609      bottom: this.width + 50,
610    });
611    canvas.detachPen();
612  }
613
614  addWidth() {
615    this.width = (this.width + 10) % 50 + 100;
616  }
617}
618
619class MyNodeController extends NodeController {
620  public rootNode: MyFrameNode | null = null;
621
622  makeNode(context: UIContext): FrameNode | null {
623    this.rootNode = new MyFrameNode(context);
624    this.rootNode?.commonAttribute?.size({ width: 100, height: 100 }).backgroundColor(Color.Green);
625    let frameNode: FrameNode = new FrameNode(context);
626    this.rootNode.appendChild(frameNode);
627    frameNode.commonAttribute.width(10).height(10).backgroundColor(Color.Pink);
628    return this.rootNode;
629  }
630}
631
632@Entry
633@Component
634struct Index {
635  private nodeController: MyNodeController = new MyNodeController();
636
637  build() {
638    Row() {
639      Column() {
640        NodeContainer(this.nodeController)
641          .width('100%')
642          .height(200)
643          .backgroundColor('#FFF0F0F0')
644        Button('Invalidate')
645          .margin(10)
646          .onClick(() => {
647            this.nodeController?.rootNode?.addWidth();
648            this.nodeController?.rootNode?.invalidate();
649          })
650        Button('UpdateLayout')
651          .onClick(() => {
652            let node = this.nodeController.rootNode;
653            node!.offsetY = (node!.offsetY + 10) % 110;
654            this.nodeController?.rootNode?.setNeedsLayout();
655          })
656      }
657      .width('100%')
658      .height('100%')
659    }
660    .height('100%')
661  }
662}
663```
664
665## 查找节点及获取基础信息
666
667FrameNode提供了查询接口用于返回实体节点的基础信息。具体返回的信息内容参考FrameNode中提供的接口。
668
669查找获得FrameNode的方式包括三种:
670
6711. 使用[getFrameNodeById](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyid12)获取。
672
6732. 使用[getFrameNodeByUniqueId](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyuniqueid12)获取。
674
6753. 通过[无感监听](../reference/apis-arkui/js-apis-arkui-observer.md)获取。
676
677> **说明:**
678>
679> 1、当前接口提供的可查询的信息包括:
680>
681> - 节点大小:[getMeasuredSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getmeasuredsize12),[getUserConfigSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigsize12)
682>
683> - 布局信息:[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)
684>
685> - 节点信息:[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)
686>
687> 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组件等。
688
689## 获取节点位置偏移信息
690
691FrameNode提供了查询节点相对窗口、父组件以及屏幕位置偏移的信息接口([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))。
692
693```ts
694import { NodeController, FrameNode, UIContext } from '@kit.ArkUI';
695
696const TEST_TAG: string = "FrameNode"
697
698class MyNodeController extends NodeController {
699  public frameNode: FrameNode | null = null;
700  private rootNode: FrameNode | null = null;
701
702  makeNode(uiContext: UIContext): FrameNode | null {
703    this.rootNode = new FrameNode(uiContext);
704    this.frameNode = new FrameNode(uiContext);
705    this.rootNode.appendChild(this.frameNode);
706    return this.rootNode;
707  }
708
709  getPositionToWindow() {
710    let positionToWindow = this.rootNode?.getPositionToWindow(); // 获取FrameNode相对于窗口的位置偏移
711    console.log(`${TEST_TAG} ${JSON.stringify(positionToWindow)}`);
712  }
713
714  getPositionToParent() {
715    let positionToParent = this.rootNode?.getPositionToParent(); // 获取FrameNode相对于父组件的位置偏移
716    console.log(`${TEST_TAG} ${JSON.stringify(positionToParent)}`);
717  }
718
719  getPositionToScreen() {
720    let positionToScreen = this.rootNode?.getPositionToScreen(); // 获取FrameNode相对于屏幕的位置偏移
721    console.log(`${TEST_TAG} ${JSON.stringify(positionToScreen)}`);
722  }
723
724  getPositionToWindowWithTransform() {
725    let positionToWindowWithTransform =
726      this.rootNode?.getPositionToWindowWithTransform(); // 获取FrameNode相对于窗口带有绘制属性的位置偏移
727    console.log(`${TEST_TAG} ${JSON.stringify(positionToWindowWithTransform)}`);
728  }
729
730  getPositionToParentWithTransform() {
731    let positionToParentWithTransform =
732      this.rootNode?.getPositionToParentWithTransform(); // 获取FrameNode相对于父组件带有绘制属性的位置偏移
733    console.log(`${TEST_TAG} ${JSON.stringify(positionToParentWithTransform)}`);
734  }
735
736  getPositionToScreenWithTransform() {
737    let positionToScreenWithTransform =
738      this.rootNode?.getPositionToScreenWithTransform(); // 获取FrameNode相对于屏幕带有绘制属性的位置偏移
739    console.log(`${TEST_TAG} ${JSON.stringify(positionToScreenWithTransform)}`);
740  }
741}
742
743@Entry
744@Component
745struct Index {
746  private myNodeController: MyNodeController = new MyNodeController();
747
748  build() {
749    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
750      Button("getPositionToWindow")
751        .width(300)
752        .onClick(() => {
753          this.myNodeController.getPositionToWindow();
754        })
755      Button("getPositionToParent")
756        .width(300)
757        .onClick(() => {
758          this.myNodeController.getPositionToParent();
759        })
760      Button("getPositionToScreen")
761        .width(300)
762        .onClick(() => {
763          this.myNodeController.getPositionToScreen();
764        })
765      Button("getPositionToParentWithTransform")
766        .width(300)
767        .onClick(() => {
768          this.myNodeController.getPositionToParentWithTransform();
769        })
770      Button("getPositionToWindowWithTransform")
771        .width(300)
772        .onClick(() => {
773          this.myNodeController.getPositionToWindowWithTransform();
774        })
775      Button("getPositionToScreenWithTransform")
776        .width(300)
777        .onClick(() => {
778          this.myNodeController.getPositionToScreenWithTransform();
779        })
780      Column() {
781        Text("This is a NodeContainer.")
782          .textAlign(TextAlign.Center)
783          .borderRadius(10)
784          .backgroundColor(0xFFFFFF)
785          .width('100%')
786          .fontSize(16)
787        NodeContainer(this.myNodeController)
788          .borderWidth(1)
789          .width(300)
790          .height(100)
791      }
792    }
793    .padding({
794      left: 35,
795      right: 35,
796      top: 35,
797      bottom: 35
798    })
799    .width("100%")
800    .height("100%")
801  }
802}
803```
804
805## 通过typeNode创建具体类型的FrameNode节点
806
807通过TypeNode创建具体类型的FrameNode节点,可以根据属性获取接口来检索用户设置的属性信息。
808
809```ts
810import { NodeController, FrameNode, UIContext, BuilderNode, typeNode } from '@kit.ArkUI';
811
812class Params {
813  text: string = "";
814
815  constructor(text: string) {
816    this.text = text;
817  }
818}
819
820@Builder
821function buildText(params: Params) {
822  Column() {
823    Text(params.text)
824      .id("buildText")
825      .border({ width: 1 })
826      .padding(1)
827      .fontSize(25)
828      .fontWeight(FontWeight.Bold)
829      .margin({ top: 10 })
830      .visibility(Visibility.Visible)
831      .opacity(0.7)
832      .customProperty("key1", "value1")
833      .width(300)
834  }
835}
836
837const TEST_TAG: string = "FrameNode"
838
839class MyNodeController extends NodeController {
840  public frameNode: typeNode.Column | null = null;
841  public uiContext: UIContext | undefined = undefined;
842  private rootNode: FrameNode | null = null;
843  private textNode: BuilderNode<[Params]> | null = null;
844  public textTypeNode: typeNode.Text | null = null;
845  private message: string = "DEFAULT";
846
847  makeNode(uiContext: UIContext): FrameNode | null {
848    this.rootNode = new FrameNode(uiContext);
849    this.uiContext = uiContext;
850    this.frameNode = typeNode.createNode(uiContext, "Column");
851    this.frameNode.attribute
852      .width("100%")
853      .height("100%")
854    this.rootNode.appendChild(this.frameNode);
855    this.textNode = new BuilderNode(uiContext);
856    this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message));
857    this.frameNode.appendChild(this.textNode.getFrameNode());
858    this.textTypeNode = typeNode.createNode(uiContext, "Text");
859    this.textTypeNode.initialize("textTypeNode")
860      .fontSize(25)
861      .visibility(Visibility.Visible)
862      .id("textTypeNode")
863    this.frameNode.appendChild(this.textTypeNode);
864    return this.rootNode;
865  }
866
867  removeChild(frameNode: FrameNode) {
868    let parent = frameNode.getParent();
869    if (parent) {
870      parent.removeChild(frameNode);
871
872    }
873  }
874
875  getUserConfigBorderWidth(frameNode: FrameNode) {
876    let userConfigBorderWidth = frameNode?.getUserConfigBorderWidth(); // 获取用户设置的边框宽度
877    console.log(`${TEST_TAG} ${JSON.stringify(userConfigBorderWidth)}`);
878  }
879
880  getUserConfigPadding(frameNode: FrameNode) {
881    let userConfigPadding = frameNode?.getUserConfigPadding(); // 获取用户设置的内边距
882    console.log(`${TEST_TAG} ${JSON.stringify(userConfigPadding)}`);
883  }
884
885  getUserConfigMargin(frameNode: FrameNode) {
886    let userConfigMargin = frameNode?.getUserConfigMargin(); // 获取用户设置的外边距
887    console.log(`${TEST_TAG} ${JSON.stringify(userConfigMargin)}`);
888  }
889
890  getUserConfigSize(frameNode: FrameNode) {
891    let userConfigSize = frameNode?.getUserConfigSize(); // 获取用户设置的宽高
892    console.log(`${TEST_TAG} ${JSON.stringify(userConfigSize)}`);
893  }
894
895  getId(frameNode: FrameNode) {
896    let id = frameNode?.getId(); // 获取用户设置的节点ID
897    console.log(`${TEST_TAG} ${id}`);
898  }
899
900  getUniqueId(frameNode: FrameNode) {
901    let uniqueId = frameNode?.getUniqueId(); // 获取系统分配的唯一标识的节点UniqueID
902    console.log(`${TEST_TAG} ${uniqueId}`);
903  }
904
905  getNodeType(frameNode: FrameNode) {
906    let nodeType = frameNode?.getNodeType(); // 获取节点的类型
907    console.log(`${TEST_TAG} ${nodeType}`);
908  }
909
910  getOpacity(frameNode: FrameNode) {
911    let opacity = frameNode?.getOpacity(); // 获取节点的不透明度
912    console.log(`${TEST_TAG} ${JSON.stringify(opacity)}`);
913  }
914
915  isVisible(frameNode: FrameNode) {
916    let visible = frameNode?.isVisible(); // 获取节点是否可见
917    console.log(`${TEST_TAG} ${JSON.stringify(visible)}`);
918  }
919
920  isClipToFrame(frameNode: FrameNode) {
921    let clipToFrame = frameNode?.isClipToFrame(); // 获取节点是否是剪裁到组件区域
922    console.log(`${TEST_TAG} ${JSON.stringify(clipToFrame)}`);
923  }
924
925  isAttached(frameNode: FrameNode) {
926    let attached = frameNode?.isAttached(); // 获取节点是否被挂载到主节点树上
927    console.log(`${TEST_TAG} ${JSON.stringify(attached)}`);
928  }
929
930  getInspectorInfo(frameNode: FrameNode) {
931    let inspectorInfo = frameNode?.getInspectorInfo(); // 获取节点的结构信息
932    console.log(`${TEST_TAG} ${JSON.stringify(inspectorInfo)}`);
933  }
934}
935
936@Entry
937@Component
938struct Index {
939  private myNodeController: MyNodeController = new MyNodeController();
940  @State index: number = 0;
941
942  build() {
943    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
944      Column() {
945        Text("This is a NodeContainer.")
946          .textAlign(TextAlign.Center)
947          .borderRadius(10)
948          .backgroundColor(0xFFFFFF)
949          .width('100%')
950          .fontSize(16)
951        NodeContainer(this.myNodeController)
952          .borderWidth(1)
953          .width(300)
954          .height(100)
955      }
956
957      Button("getUserConfigBorderWidth")
958        .width(300)
959        .onClick(() => {
960          const uiContext: UIContext = this.getUIContext();
961          if (uiContext) {
962            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
963            if (node) {
964              this.myNodeController.getUserConfigBorderWidth(node);
965            }
966          }
967        })
968      Button("getUserConfigPadding")
969        .width(300)
970        .onClick(() => {
971          const uiContext: UIContext = this.getUIContext();
972          if (uiContext) {
973            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
974            if (node) {
975              this.myNodeController.getUserConfigPadding(node);
976            }
977          }
978        })
979      Button("getUserConfigMargin")
980        .width(300)
981        .onClick(() => {
982          const uiContext: UIContext = this.getUIContext();
983          if (uiContext) {
984            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
985            if (node) {
986              this.myNodeController.getUserConfigMargin(node);
987            }
988          }
989        })
990      Button("getUserConfigSize")
991        .width(300)
992        .onClick(() => {
993          const uiContext: UIContext = this.getUIContext();
994          if (uiContext) {
995            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
996            if (node) {
997              this.myNodeController.getUserConfigSize(node);
998            }
999          }
1000        })
1001      Button("getId")
1002        .width(300)
1003        .onClick(() => {
1004          const uiContext: UIContext = this.getUIContext();
1005          if (uiContext) {
1006            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
1007            if (node) {
1008              this.myNodeController.getId(node);
1009            }
1010          }
1011        })
1012      Button("getUniqueId")
1013        .width(300)
1014        .onClick(() => {
1015          const uiContext: UIContext = this.getUIContext();
1016          if (uiContext) {
1017            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
1018            if (node) {
1019              this.myNodeController.getUniqueId(node);
1020            }
1021          }
1022        })
1023      Button("getNodeType")
1024        .width(300)
1025        .onClick(() => {
1026          const uiContext: UIContext = this.getUIContext();
1027          if (uiContext) {
1028            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
1029            if (node) {
1030              this.myNodeController.getNodeType(node);
1031            }
1032          }
1033        })
1034      Button("getOpacity")
1035        .width(300)
1036        .onClick(() => {
1037          const uiContext: UIContext = this.getUIContext();
1038          if (uiContext) {
1039            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
1040            if (node) {
1041              this.myNodeController.getOpacity(node);
1042            }
1043          }
1044        })
1045      Button("isVisible")
1046        .width(300)
1047        .onClick(() => {
1048          const uiContext: UIContext = this.getUIContext();
1049          if (uiContext) {
1050            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
1051            if (node) {
1052              this.myNodeController.isVisible(node);
1053            }
1054          }
1055        })
1056      Button("isClipToFrame")
1057        .width(300)
1058        .onClick(() => {
1059          const uiContext: UIContext = this.getUIContext();
1060          if (uiContext) {
1061            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
1062            if (node) {
1063              this.myNodeController.isClipToFrame(node);
1064            }
1065          }
1066        })
1067      Button("isAttached")
1068        .width(300)
1069        .onClick(() => {
1070          const uiContext: UIContext = this.getUIContext();
1071          if (uiContext) {
1072            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
1073            if (node) {
1074              this.myNodeController.isAttached(node);
1075            }
1076          }
1077        })
1078      Button("remove Text")
1079        .width(300)
1080        .onClick(() => {
1081          const uiContext: UIContext = this.getUIContext();
1082          if (uiContext) {
1083            const node: FrameNode | null = uiContext.getFrameNodeById("textTypeNode") || null;
1084            if (node) {
1085              this.myNodeController.removeChild(node);
1086              this.myNodeController.isAttached(node);
1087            }
1088          }
1089        })
1090      Button("getInspectorInfo")
1091        .width(300)
1092        .onClick(() => {
1093          const uiContext: UIContext = this.getUIContext();
1094          if (uiContext) {
1095            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
1096            if (node) {
1097              this.myNodeController.getInspectorInfo(node);
1098            }
1099          }
1100        })
1101      Button("getCustomProperty")
1102        .width(300)
1103        .onClick(() => {
1104          const uiContext: UIContext = this.getUIContext();
1105          if (uiContext) {
1106            const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null;
1107            if (node) {
1108              const property = node.getCustomProperty("key1");
1109              console.log(TEST_TAG, JSON.stringify(property));
1110            }
1111          }
1112        })
1113    }
1114    .padding({
1115      left: 35,
1116      right: 35,
1117      top: 35,
1118      bottom: 35
1119    })
1120    .width("100%")
1121    .height("100%")
1122  }
1123}
1124```
1125
1126## 解除当前FrameNode对象对实体FrameNode节点的引用关系
1127
1128使用[dispose](../reference/apis-arkui/js-apis-arkui-frameNode.md#dispose12)接口可以立即解除当前FrameNode对象对实体FrameNode节点的引用关系。
1129
1130> **说明:**
1131>
1132> 在调用dispose方法后,FrameNode对象不再对应任何实际的FrameNode节点。此时,若尝试调用以下查询接口:getMeasuredSize、getLayoutPosition、getUserConfigBorderWidth、getUserConfigPadding、getUserConfigMargin、getUserConfigSize,将导致应用程序触发jscrash。
1133>
1134> 通过[getUniqueId](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuniqueid12)可以判断当前FrameNode是否对应一个实体FrameNode节点。当UniqueId大于0时表示该对象对应一个实体FrameNode节点。
1135
1136```ts
1137import { NodeController, FrameNode, BuilderNode } from '@kit.ArkUI';
1138
1139const TEST_TAG: string = "FrameNode";
1140
1141@Component
1142struct TestComponent {
1143  build() {
1144    Column() {
1145      Text('This is a BuilderNode.')
1146        .fontSize(16)
1147        .fontWeight(FontWeight.Bold)
1148    }
1149    .width('100%')
1150    .backgroundColor(Color.Gray)
1151  }
1152
1153  aboutToAppear() {
1154    console.error(TEST_TAG + ' aboutToAppear');
1155  }
1156
1157  aboutToDisappear() {
1158    console.error(TEST_TAG + ' aboutToDisappear');
1159  }
1160}
1161
1162@Builder
1163function buildComponent() {
1164  TestComponent()
1165}
1166
1167class MyNodeController extends NodeController {
1168  private rootNode: FrameNode | null = null;
1169  private builderNode: BuilderNode<[]> | null = null;
1170
1171  makeNode(uiContext: UIContext): FrameNode | null {
1172    this.rootNode = new FrameNode(uiContext);
1173    this.builderNode = new BuilderNode(uiContext, { selfIdealSize: { width: 200, height: 100 } });
1174    this.builderNode.build(new WrappedBuilder(buildComponent));
1175
1176    const rootRenderNode = this.rootNode.getRenderNode();
1177    if (rootRenderNode !== null) {
1178      rootRenderNode.size = { width: 200, height: 200 };
1179      rootRenderNode.backgroundColor = 0xff00ff00;
1180      rootRenderNode.appendChild(this.builderNode!.getFrameNode()!.getRenderNode());
1181    }
1182
1183    return this.rootNode;
1184  }
1185
1186  printUniqueId(): void {
1187    if (this.rootNode !== null && this.builderNode !== null) {
1188      console.log(`${TEST_TAG} rootNode's uniqueId: ${this.rootNode.getUniqueId()}`);
1189      const frameNode = this.builderNode.getFrameNode();
1190      if (frameNode) {
1191        console.log(`${TEST_TAG} the uniqueId of builderNode's framenode: ${frameNode.getUniqueId()}`);
1192      } else {
1193        console.log(`${TEST_TAG} builderNode's framenode is undefined`);
1194      }
1195    }
1196  }
1197
1198  disposeFrameNode(): void {
1199    if (this.rootNode !== null && this.builderNode !== null) {
1200      console.log(`${TEST_TAG} disposeFrameNode`);
1201      this.rootNode.removeChild(this.builderNode.getFrameNode());
1202      this.builderNode.dispose();
1203
1204      this.rootNode.dispose();
1205    }
1206  }
1207
1208  removeBuilderNode(): void {
1209    const rootRenderNode = this.rootNode!.getRenderNode();
1210    if (rootRenderNode !== null && this.builderNode !== null && this.builderNode.getFrameNode() !== null) {
1211      rootRenderNode.removeChild(this.builderNode!.getFrameNode()!.getRenderNode());
1212    }
1213  }
1214}
1215
1216@Entry
1217@Component
1218struct Index {
1219  private myNodeController: MyNodeController = new MyNodeController();
1220
1221  build() {
1222    Column({ space: 4 }) {
1223      NodeContainer(this.myNodeController)
1224      Button('FrameNode dispose')
1225        .onClick(() => {
1226          this.myNodeController.printUniqueId();
1227          this.myNodeController.disposeFrameNode();
1228          this.myNodeController.printUniqueId();
1229        })
1230        .width('100%')
1231    }
1232  }
1233}
1234```
1235
1236## FrameNode的数据懒加载能力
1237
1238提供[NodeAdapter](../reference/apis-arkui/js-apis-arkui-frameNode.md#nodeadapter12)对象替代ArkTS侧的LazyForEach功能,提供自定义节点的数据懒加载功能,实现按需迭代数据。
1239
1240> **说明:**
1241>
1242> 入参不能为负数,入参为负数时不做处理。
1243
1244```ts
1245import { FrameNode, NodeController, NodeAdapter, typeNode } from '@kit.ArkUI';
1246
1247const TEST_TAG: string = "FrameNode";
1248
1249class MyNodeAdapter extends NodeAdapter {
1250  uiContext: UIContext
1251  cachePool: Array<FrameNode> = new Array();
1252  changed: boolean = false
1253  reloadTimes: number = 0;
1254  data: Array<string> = new Array();
1255  hostNode?: FrameNode
1256
1257  constructor(uiContext: UIContext, count: number) {
1258    super();
1259    this.uiContext = uiContext;
1260    this.totalNodeCount = count;
1261    this.loadData();
1262  }
1263
1264  reloadData(count: number): void {
1265    this.reloadTimes++;
1266    NodeAdapter.attachNodeAdapter(this, this.hostNode);
1267    this.totalNodeCount = count;
1268    this.loadData();
1269    this.reloadAllItems();
1270  }
1271
1272  refreshData(): void {
1273    let items = this.getAllAvailableItems()
1274    console.log(TEST_TAG + " get All items:" + items.length);
1275    this.totalNodeCount -= 1;
1276    this.reloadAllItems();
1277  }
1278
1279  detachData(): void {
1280    NodeAdapter.detachNodeAdapter(this.hostNode);
1281    this.reloadTimes = 0;
1282  }
1283
1284  loadData(): void {
1285    for (let i = 0; i < this.totalNodeCount; i++) {
1286      this.data[i] = "Adapter ListItem " + i + " r:" + this.reloadTimes;
1287    }
1288  }
1289
1290  changeData(from: number, count: number): void {
1291    this.changed = !this.changed;
1292    for (let i = 0; i < count; i++) {
1293      let index = i + from;
1294      this.data[index] = "Adapter ListItem " + (this.changed ? "changed:" : "") + index + " r:" + this.reloadTimes;
1295    }
1296    this.reloadItem(from, count);
1297  }
1298
1299  insertData(from: number, count: number): void {
1300    for (let i = 0; i < count; i++) {
1301      let index = i + from;
1302      this.data.splice(index, 0, "Adapter ListItem " + from + "-" + i);
1303    }
1304    this.insertItem(from, count);
1305    this.totalNodeCount += count;
1306    console.log(TEST_TAG + " after insert count:" + this.totalNodeCount);
1307  }
1308
1309  removeData(from: number, count: number): void {
1310    let arr = this.data.splice(from, count);
1311    this.removeItem(from, count);
1312    this.totalNodeCount -= arr.length;
1313    console.log(TEST_TAG + " after remove count:" + this.totalNodeCount);
1314  }
1315
1316  moveData(from: number, to: number): void {
1317    let tmp = this.data.splice(from, 1);
1318    this.data.splice(to, 0, tmp[0]);
1319    this.moveItem(from, to);
1320  }
1321
1322  onAttachToNode(target: FrameNode): void {
1323    console.log(TEST_TAG + " onAttachToNode id:" + target.getUniqueId());
1324    this.hostNode = target;
1325  }
1326
1327  onDetachFromNode(): void {
1328    console.log(TEST_TAG + " onDetachFromNode");
1329  }
1330
1331  onGetChildId(index: number): number {
1332    console.log(TEST_TAG + " onGetChildId:" + index);
1333    return index;
1334  }
1335
1336  onCreateChild(index: number): FrameNode {
1337    console.log(TEST_TAG + " onCreateChild:" + index);
1338    if (this.cachePool.length > 0) {
1339      let cacheNode = this.cachePool.pop();
1340      if (cacheNode !== undefined) {
1341        console.log(TEST_TAG + " onCreateChild reused id:" + cacheNode.getUniqueId());
1342        let text = cacheNode?.getFirstChild();
1343        let textNode = text as typeNode.Text;
1344        textNode?.initialize(this.data[index]).fontSize(20);
1345        return cacheNode;
1346      }
1347    }
1348    console.log(TEST_TAG + " onCreateChild createNew");
1349    let itemNode = typeNode.createNode(this.uiContext, "ListItem");
1350    let textNode = typeNode.createNode(this.uiContext, "Text");
1351    textNode.initialize(this.data[index]).fontSize(20);
1352    itemNode.appendChild(textNode);
1353    return itemNode;
1354  }
1355
1356  onDisposeChild(id: number, node: FrameNode): void {
1357    console.log(TEST_TAG + " onDisposeChild:" + id);
1358    if (this.cachePool.length < 10) {
1359      if (!this.cachePool.includes(node)) {
1360        console.log(TEST_TAG + " caching node id:" + node.getUniqueId());
1361        this.cachePool.push(node);
1362      }
1363    } else {
1364      node.dispose();
1365    }
1366  }
1367
1368  onUpdateChild(id: number, node: FrameNode): void {
1369    let index = id;
1370    let text = node.getFirstChild();
1371    let textNode = text as typeNode.Text;
1372    textNode?.initialize(this.data[index]).fontSize(20);
1373  }
1374}
1375
1376class MyNodeAdapterController extends NodeController {
1377  rootNode: FrameNode | null = null;
1378  nodeAdapter: MyNodeAdapter | null = null;
1379
1380  makeNode(uiContext: UIContext): FrameNode | null {
1381    this.rootNode = new FrameNode(uiContext);
1382    let listNode = typeNode.createNode(uiContext, "List");
1383    listNode.initialize({ space: 3 }).borderWidth(2).borderColor(Color.Black);
1384    this.rootNode.appendChild(listNode);
1385    this.nodeAdapter = new MyNodeAdapter(uiContext, 100);
1386    NodeAdapter.attachNodeAdapter(this.nodeAdapter, listNode);
1387    return this.rootNode;
1388  }
1389}
1390
1391@Entry
1392@Component
1393struct ListNodeTest {
1394  adapterController: MyNodeAdapterController = new MyNodeAdapterController();
1395
1396  build() {
1397    Column() {
1398      Text("ListNode Adapter");
1399      NodeContainer(this.adapterController)
1400        .width(300).height(300)
1401        .borderWidth(1).borderColor(Color.Black);
1402      Row() {
1403        Button("Reload")
1404          .onClick(() => {
1405            this.adapterController.nodeAdapter?.reloadData(50);
1406          })
1407        Button("Change")
1408          .onClick(() => {
1409            this.adapterController.nodeAdapter?.changeData(5, 10)
1410          })
1411        Button("Insert")
1412          .onClick(() => {
1413            this.adapterController.nodeAdapter?.insertData(10, 10);
1414          })
1415      }
1416
1417      Row() {
1418        Button("Remove")
1419          .onClick(() => {
1420            this.adapterController.nodeAdapter?.removeData(10, 10);
1421          })
1422        Button("Move")
1423          .onClick(() => {
1424            this.adapterController.nodeAdapter?.moveData(2, 5);
1425          })
1426        Button("Refresh")
1427          .onClick(() => {
1428            this.adapterController.nodeAdapter?.refreshData();
1429          })
1430        Button("Detach")
1431          .onClick(() => {
1432            this.adapterController.nodeAdapter?.detachData();
1433          })
1434      }
1435    }.borderWidth(1)
1436    .width("100%")
1437  }
1438}
1439```
1440