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