1# FrameNode
2
3## 概述
4
5对于具备自己前端定义的三方框架,需要将特定的dsl转换成为ArkUI的声明式描述。这个转换过程需依赖额外的数据驱动绑定至[Builder](../quick-start/arkts-builder.md)中,转换比较复杂且性能较低。这一类框架一般依赖系统ArkUI框架的布局、事件能力,以及最基础的节点操作和自定义能力,大部分组件通过自定义完成,但是需要使用部分原生组件混合显示。[FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md)的设计就是为了解决上述的问题。
6
7FrameNode表示组件树的实体节点,配合自定义占位容器组件[NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md)等,在占位容器内挂载一棵自定义的节点树,并对这个节点树中的节点进行动态的增加、修改、删除等操作。基础的FrameNode可以设置通用属性、设置事件回调,并提供完整的自定义能力,包括自定义测量、布局以及绘制。
8
9除此之外,ArkUI框架还提供获取和遍历获得原生组件对应的代理FrameNode对象的能力,下文简称代理节点。代理节点可以用于需要遍历整个UI的树形结构,并支持获取原生组件节点的具体信息或者额外注册组件的事件监听回调。
10
11## 创建和删除节点
12
13FrameNode提供了节点创建和删除的能力。可以通过FrameNode的构造函数创建自定义FrameNode节点,通过构造函数创建的节点对应一个实体的节点。同时,可以通过FrameNode中的[dispose](../reference/apis-arkui/js-apis-arkui-frameNode.md#dispose12)接口来实现与实体节点的绑定关系的解除。
14
15> **说明:**
16>
17> - 在创建FrameNode对象的时候需要传入必选参数UIContext,若未传入UIContext对象或者传入不合法,则节点创建抛出异常。
18>
19> - 自定义占位组件将节点进行显示的时候需要保证UI上下文一致,否则会出现显示异常。
20>
21> - 若不持有FrameNode对象,则该对象会在GC的时候被回收。
22
23## 判断节点是否可修改
24
25[isModifiable](../reference/apis-arkui/js-apis-arkui-frameNode.md#ismodifiable12)用于查询当前节点类型是否为原生组件的代理节点。当FrameNode节点作为原生组件的代理节点的时候,该节点不可修改。即无法修改代理节点的自身属性以及其子节点的结构。
26
27## 获取对应的RenderNode节点
28
29FrameNode提供了[getRenderNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#getrendernode)接口,用于获取FrameNode中的RenderNode。可以通过对获取到的RenderNode对象进行操作,动态修改FrameNode上绘制相关的属性,具体可修改的属性参考RenderNode的接口。
30
31> **说明:**
32>
33> - 无法获取原生组件代理FrameNode的RenderNode对象。
34> 
35> - BuilderNode中调用[getFrameNode](../reference/apis-arkui/js-apis-arkui-builderNode.md#getframenode)获取得到的FrameNode节点对象中,可以通过getRenderNode获取对应的根节点的RenderNode对象。
36
37## 操作节点树
38
39FrameNode提供了节点的增、删、查、改的能力,能够修改非代理节点的子树结构。可以对所有FrameNode的节点的父子节点做出查询操作,并返回查询结果。
40
41> **说明:**
42>
43> 对节点进行增、删、改操作的时候,会对非法操作抛出异常信息。
44>
45> 通过查询获得的原生组件的代理节点,仅具备查询节点信息的作用,不具备修改节点属性的功能。代理节点不持有组件的实体节点,即不影响对应的节点的生命周期。
46>
47> 查询节点仅查询获得UI相关的节点,不返回语法节点。
48>
49> 使用自定义组件的场景下,可能查询获得自定义组件的新增节点,节点类型为“\_\_Common\_\_”。
50
51```ts
52import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI';
53import { BusinessError } from '@kit.BasicServicesKit';
54
55const TEST_TAG: string = "FrameNode"
56
57class Params {
58  text: string = "this is a text"
59}
60
61@Builder
62function buttonBuilder(params: Params) {
63  Column({ space: 10 }) {
64    Button(params.text)
65      .fontSize(12)
66      .borderRadius(8)
67      .borderWidth(2)
68      .backgroundColor(Color.Orange)
69
70    Button(params.text)
71      .fontSize(12)
72      .borderRadius(8)
73      .borderWidth(2)
74      .backgroundColor(Color.Pink)
75  }
76}
77
78class MyNodeController extends NodeController {
79  public buttonNode: BuilderNode<[Params]> | null = null;
80  public frameNode: FrameNode | null = null;
81  public childList: Array<FrameNode> = new Array<FrameNode>();
82  public rootNode: FrameNode | null = null;
83  private uiContext: UIContext | null = null;
84  private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(buttonBuilder);
85
86  makeNode(uiContext: UIContext): FrameNode | null {
87    this.uiContext = uiContext;
88    if (this.rootNode == null) {
89      this.rootNode = new FrameNode(uiContext);
90      this.rootNode.commonAttribute
91        .width("50%")
92        .height(100)
93        .borderWidth(1)
94        .backgroundColor(Color.Gray)
95    }
96
97    if (this.frameNode == null) {
98      this.frameNode = new FrameNode(uiContext);
99      this.frameNode.commonAttribute
100        .width("100%")
101        .height(50)
102        .borderWidth(1)
103        .position({ x: 200, y: 0 })
104        .backgroundColor(Color.Pink);
105      this.rootNode.appendChild(this.frameNode);
106    }
107    if (this.buttonNode == null) {
108      this.buttonNode = new BuilderNode<[Params]>(uiContext);
109      this.buttonNode.build(this.wrapBuilder, { text: "This is a Button" })
110      this.rootNode.appendChild(this.buttonNode.getFrameNode())
111    }
112    return this.rootNode;
113  }
114
115  operationFrameNodeWithFrameNode(frameNode: FrameNode | undefined | null) {
116    if (frameNode) {
117      console.log(TEST_TAG + " get ArkTSNode success.")
118      console.log(TEST_TAG + " check rootNode whether is modifiable " + frameNode.isModifiable());
119    }
120    if (this.uiContext) {
121      let frameNode1 = new FrameNode(this.uiContext);
122      let frameNode2 = new FrameNode(this.uiContext);
123      frameNode1.commonAttribute.size({ width: 50, height: 50 })
124        .backgroundColor(Color.Black)
125        .position({ x: 50, y: 60 })
126      frameNode2.commonAttribute.size({ width: 50, height: 50 })
127        .backgroundColor(Color.Orange)
128        .position({ x: 120, y: 60 })
129      try {
130        frameNode?.appendChild(frameNode1);
131        console.log(TEST_TAG + " appendChild success ");
132      } catch (err) {
133        console.log(TEST_TAG + " appendChild fail :" + (err as BusinessError).code + " : " + (err as BusinessError).message);
134      }
135      try {
136        frameNode?.insertChildAfter(frameNode2, null);
137        console.log(TEST_TAG + " insertChildAfter success ");
138      } catch (err) {
139        console.log(TEST_TAG + " insertChildAfter fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message);
140      }
141      setTimeout(() => {
142        try {
143          frameNode?.removeChild(frameNode?.getChild(0))
144          console.log(TEST_TAG + " removeChild success ");
145        } catch (err) {
146          console.log(TEST_TAG + " removeChild fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message);
147        }
148      }, 2000)
149      setTimeout(() => {
150        try {
151          frameNode?.clearChildren();
152          console.log(TEST_TAG + " clearChildren success ");
153        } catch (err) {
154          console.log(TEST_TAG + " clearChildren fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message);
155        }
156      }, 4000)
157    }
158  }
159
160  testInterfaceAboutSearch(frameNode: FrameNode | undefined | null): string {
161    let result: string = "";
162    if (frameNode) {
163      result = result + `current node is ${frameNode.getNodeType()} \n`;
164      result = result + `parent node is ${frameNode.getParent()?.getNodeType()} \n`;
165      result = result + `child count is ${frameNode.getChildrenCount()} \n`;
166      result = result + `first child node is ${frameNode.getFirstChild()?.getNodeType()} \n`;
167      result = result + `second child node is ${frameNode.getChild(1)?.getNodeType()} \n`;
168      result = result + `previousSibling node is ${frameNode.getPreviousSibling()?.getNodeType()} \n`;
169      result = result + `nextSibling node is ${frameNode.getNextSibling()?.getNodeType()} \n`;
170    }
171    return result;
172  }
173
174  checkAppendChild(parent: FrameNode | undefined | null, child: FrameNode | undefined | null) {
175    try {
176      if (parent && child) {
177        parent.appendChild(child);
178        console.log(TEST_TAG + " appendChild success ");
179      }
180    } catch (err) {
181      console.log(TEST_TAG + " appendChild fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message);
182    }
183  }
184}
185
186@Entry
187@Component
188struct Index {
189  @State index: number = 0;
190  @State result: string = ""
191  private myNodeController: MyNodeController = new MyNodeController();
192
193  build() {
194    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
195      List({ space: 20, initialIndex: 0 }) {
196        ListItem() {
197          Column({ space: 5 }) {
198            Text("验证FrameNode子节点的增、删、改功能")
199            Button("对自定义FrameNode进行操作")
200              .fontSize(16)
201              .width(400)
202              .onClick(() => {
203                // 对FrameNode节点进行增、删、改操作,正常实现。
204                this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.frameNode);
205              })
206            Button("对BuilderNode中的代理节点进行操作")
207              .fontSize(16)
208              .width(400)
209              .onClick(() => {
210                // 对BuilderNode代理节点进行增、删、改操作,捕获异常信息。
211                this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.buttonNode?.getFrameNode());
212              })
213            Button("对原生组件中的代理节点进行操作")
214              .fontSize(16)
215              .width(400)
216              .onClick(() => {
217                // 对代理节点进行增、删、改操作,捕获异常信息。
218                this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.rootNode?.getParent());
219              })
220          }
221        }
222
223        ListItem() {
224          Column({ space: 5 }) {
225            Text("验证FrameNode添加子节点的特殊场景")
226            Button("新增BuilderNode的代理节点")
227              .fontSize(16)
228              .width(400)
229              .onClick(() => {
230                let buttonNode = new BuilderNode<[Params]>(this.getUIContext());
231                buttonNode.build(wrapBuilder<[Params]>(buttonBuilder), { text: "BUTTON" })
232                this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, buttonNode?.getFrameNode());
233              })
234            Button("新增原生组件代理节点")
235              .fontSize(16)
236              .width(400)
237              .onClick(() => {
238                this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, this.myNodeController?.rootNode?.getParent());
239              })
240            Button("新增已有父节点的自定义节点")
241              .fontSize(16)
242              .width(400)
243              .onClick(() => {
244                this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, this.myNodeController?.rootNode);
245              })
246          }
247        }
248
249        ListItem() {
250          Column({ space: 5 }) {
251            Text("验证FrameNode节点的查询功能")
252            Button("对自定义FrameNode进行操作")
253              .fontSize(16)
254              .width(400)
255              .onClick(() => {
256                // 对FrameNode节点进行进行查询。当前节点为NodeContainer的子节点。
257                this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode);
258                setTimeout(() => {
259                  // 对FrameNode节点进行进行查询。rootNode下的第一个子节点。
260                  this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.frameNode);
261                }, 2000)
262              })
263            Button("对BuilderNode中的代理节点进行操作")
264              .fontSize(16)
265              .width(400)
266              .onClick(() => {
267                // 对BuilderNode代理节点进行进行查询。当前节点为BuilderNode中的Column节点。
268                this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.buttonNode?.getFrameNode());
269              })
270            Button("对原生组件中的代理节点进行操作")
271              .fontSize(16)
272              .width(400)
273              .onClick(() => {
274                // 对代理节点进行查询。当前节点为NodeContainer。
275                this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode?.getParent());
276              })
277          }
278        }
279      }.height("50%")
280
281      Text(`Result:\n${this.result}`)
282        .fontSize(16)
283        .width(400)
284        .height(200)
285        .padding(30)
286        .borderWidth(1)
287      Column() {
288        Text("This is a NodeContainer.")
289          .textAlign(TextAlign.Center)
290          .borderRadius(10)
291          .backgroundColor(0xFFFFFF)
292          .width('100%')
293          .fontSize(16)
294        NodeContainer(this.myNodeController)
295          .borderWidth(1)
296          .width(400)
297          .height(150)
298      }
299    }
300    .padding({ left: 35, right: 35, top: 35, bottom: 35 })
301    .width("100%")
302    .height("100%")
303  }
304}
305```
306
307## 设置节点通用属性和事件回调
308
309FrameNode提供了[commonAttribute](../reference/apis-arkui/js-apis-arkui-frameNode.md#commonattribute12)和[commonEvent](../reference/apis-arkui/js-apis-arkui-frameNode.md#commonevent12)两个对象用于对设置节点的[通用属性](../reference/apis-arkui/arkui-ts/ts-universal-attributes-size.md)和[设置事件回调](../reference/apis-arkui/arkui-ts/ts-uicommonevent.md)。
310
311> **说明:**
312> 
313> - 由于代理节点的属性不可修改,因此通过代理节点的commonAttribute修改节点的基础属性不生效。
314> 
315> - 设置的基础事件与原生组件定义的事件平行,参与事件竞争。设置的基础事件不覆盖原生组件事件。同时设置两个事件回调的时候,优先回调原生组件事件。
316
317```ts
318import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI'
319
320class Params {
321  text: string = "this is a text"
322}
323
324@Builder
325function buttonBuilder(params: Params) {
326  Button(params.text)
327    .fontSize(12)
328    .borderRadius(8)
329    .borderWidth(2)
330    .backgroundColor(Color.Orange)
331    .onClick((event: ClickEvent) => {
332      console.log(`Button ${JSON.stringify(event)}`);
333    })
334}
335
336class MyNodeController extends NodeController {
337  public buttonNode: BuilderNode<[Params]> | null = null;
338  public frameNode: FrameNode | null = null;
339  public rootNode: FrameNode | null = null;
340  private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(buttonBuilder);
341
342  makeNode(uiContext: UIContext): FrameNode | null {
343    if (this.rootNode == null) {
344      this.rootNode = new FrameNode(uiContext);
345      // 对rootNode进行属性修改,该节点为自定义的FrameNode节点,修改生效
346      this.rootNode.commonAttribute
347        .width("100%")
348        .height(100)
349        .borderWidth(1)
350        .backgroundColor(Color.Gray)
351    }
352
353    if (this.frameNode == null) {
354      this.frameNode = new FrameNode(uiContext);
355      // 对frameNode进行属性修改,该节点为自定义的FrameNode节点,修改生效
356      this.frameNode.commonAttribute
357        .width("50%")
358        .height(50)
359        .borderWidth(1)
360        .backgroundColor(Color.Pink);
361      this.rootNode.appendChild(this.frameNode);
362    }
363    if (this.buttonNode == null) {
364      this.buttonNode = new BuilderNode<[Params]>(uiContext);
365      this.buttonNode.build(this.wrapBuilder, { text: "This is a Button" })
366      // 对BuilderNode中获取的FrameNode进行属性修改,该节点非自定义的FrameNode节点,修改不生效
367      this.buttonNode?.getFrameNode()?.commonAttribute.position({ x: 100, y: 100 })
368      this.rootNode.appendChild(this.buttonNode.getFrameNode())
369    }
370    return this.rootNode;
371  }
372
373  modifyNode(frameNode: FrameNode | null | undefined, sizeValue: SizeOptions, positionValue: Position) {
374    if (frameNode) {
375      frameNode.commonAttribute.size(sizeValue).position(positionValue);
376    }
377  }
378
379  addClickEvent(frameNode: FrameNode | null | undefined) {
380    if (frameNode) {
381      frameNode.commonEvent.setOnClick((event: ClickEvent) => {
382        console.log(`FrameNode ${JSON.stringify(event)}`);
383      })
384    }
385  }
386}
387
388@Entry
389@Component
390struct Index {
391  private myNodeController: MyNodeController = new MyNodeController();
392
393  build() {
394    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
395      Column({ space: 10 }) {
396        Text("修改节点通用属性-宽高")
397        Button("modify ArkTS-FrameNode")
398          .onClick(() => {
399            // 获取到的是当前页面中的开发者创建的FrameNode对象,该节点可修改。即节点大小与位置。
400            console.log("Check the weather the node can be modified " + this.myNodeController?.frameNode
401            ?.isModifiable());
402            this.myNodeController.modifyNode(this.myNodeController?.frameNode, { width: 150, height: 100 }, {
403              x: 100,
404              y: 0
405            })
406          })
407        Button("modify FrameNode get by BuilderNode")
408          .onClick(() => {
409            // 获取到的是当前页面中的BuilderNode的根节点,该节点不可修改。即节点大小与位置未发生改变。
410            console.log("Check the weather the node can be modified " + this.myNodeController?.buttonNode?.getFrameNode()
411            ?.isModifiable());
412            this.myNodeController.modifyNode(this.myNodeController?.buttonNode?.getFrameNode(), {
413              width: 100,
414              height: 100
415            }, { x: 50, y: 50 })
416          })
417        Button("modify proxyFrameNode get by search")
418          .onClick(() => {
419            // rootNode调用getParent()获取到的是当前页面中的NodeContainer节点,该节点不可修改。即节点大小与位置未发生改变。
420            console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent()
421            ?.isModifiable());
422            this.myNodeController.modifyNode(this.myNodeController?.rootNode?.getParent(), {
423              width: 500,
424              height: 500
425            }, {
426              x: 0,
427              y: 0
428            })
429          })
430      }.padding({ left: 35, right: 35, top: 35, bottom: 35 })
431
432      Column({ space: 10 }) {
433        Text("修改节点点击事件")
434        Button("add click event to ArkTS-FrameNode")
435          .onClick(() => {
436            // 获取到的是当前页面中的开发者创建的FrameNode对象,该节点可增加点击事件。
437            // 增加的点击事件参与事件竞争,即点击事件会在该节点被消费且不不再向父组件冒泡。
438            console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent()
439            ?.isModifiable());
440            this.myNodeController.addClickEvent(this.myNodeController?.frameNode)
441          })
442        Button("add click event to FrameNode get by BuilderNode")
443          .onClick(() => {
444            // 获取到的是当前页面中的BuilderNode的根节点,该类节点可增加点击事件。
445            // 点击的时候优先回调通过原生组件接口设置的click事件回调,然后回调通过commonEvent增加的click监听。
446            console.log("Check the weather the node can be modified " + this.myNodeController?.buttonNode?.getFrameNode()
447            ?.isModifiable());
448            this.myNodeController.addClickEvent(this.myNodeController?.buttonNode?.getFrameNode())
449          })
450        Button("add click event to proxyFrameNode get by search")
451          .onClick(() => {
452            // rootNode调用getParent()获取到的是当前页面中的NodeContainer节点,该类节点可增加点击事件。
453            console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent()
454            ?.isModifiable());
455            this.myNodeController.addClickEvent(this.myNodeController?.rootNode?.getParent());
456          })
457      }.padding({ left: 35, right: 35, top: 35, bottom: 35 })
458
459      NodeContainer(this.myNodeController)
460        .borderWidth(1)
461        .width("100%")
462        .height(100)
463        .onClick((event: ClickEvent) => {
464          console.log(`NodeContainer ${JSON.stringify(event)}`);
465        })
466    }
467    .padding({ left: 35, right: 35, top: 35, bottom: 35 })
468    .width("100%")
469    .height("100%")
470  }
471}
472```
473
474## 自定义测量布局与绘制
475
476通过重写[onDraw](../reference/apis-arkui/js-apis-arkui-frameNode.md#ondraw12)方法,可以自定义FrameNode的绘制内容。[invalidate](../reference/apis-arkui/js-apis-arkui-frameNode.md#invalidate12)接口可以主动触发节点的重新绘制。
477
478通过重写[onMeasure](../reference/apis-arkui/js-apis-arkui-frameNode.md#onmeasure12)可以自定义FrameNode的测量方式,使用[measure](../reference/apis-arkui/js-apis-arkui-frameNode.md#measure12)可以主动传递布局约束触发重新测量。
479
480通过重写[onLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#onlayout12)方法可以自定义FrameNode的布局方式,使用[layout](../reference/apis-arkui/js-apis-arkui-frameNode.md#layout12)方法可以主动传递位置信息并触发重新布局。
481
482[setNeedsLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#setneedslayout12)可以将当前节点标记,在下一帧触发重新布局。
483
484> **说明:**
485> 
486> - 对节点进行dispose解引用后,由于FrameNode对象不再对应一个实体节点,invalidate无法触发原有绑定节点的刷新。
487> 
488> - 通过onDraw方法进行的自定义绘制,绘制内容大小无法超出组件大小。
489
490```ts
491import { DrawContext, FrameNode, NodeController, Position, Size, UIContext, LayoutConstraint } from '@kit.ArkUI';
492import { drawing } from '@kit.ArkGraphics2D';
493
494function GetChildLayoutConstraint(constraint: LayoutConstraint, child: FrameNode): LayoutConstraint {
495  const size = child.getUserConfigSize();
496  const width = Math.max(
497    Math.min(constraint.maxSize.width, size.width.value),
498    constraint.minSize.width
499  );
500  const height = Math.max(
501    Math.min(constraint.maxSize.height, size.height.value),
502    constraint.minSize.height
503  );
504  const finalSize: Size = { width, height };
505  const res: LayoutConstraint = {
506    maxSize: finalSize,
507    minSize: finalSize,
508    percentReference: finalSize
509  };
510
511  return res;
512}
513
514class MyFrameNode extends FrameNode {
515  public width: number = 100;
516  public offsetY: number = 0;
517  private space: number = 1;
518
519  onMeasure(constraint: LayoutConstraint): void {
520    let sizeRes: Size = { width: vp2px(100), height: vp2px(100) };
521    for (let i = 0;i < this.getChildrenCount(); i++) {
522      let child = this.getChild(i);
523      if (child) {
524        let childConstraint = GetChildLayoutConstraint(constraint, child);
525        child.measure(childConstraint);
526        let size = child.getMeasuredSize();
527        sizeRes.height += size.height + this.space;
528        sizeRes.width = Math.max(sizeRes.width, size.width);
529      }
530    }
531    this.setMeasuredSize(sizeRes);
532  }
533
534  onLayout(position: Position): void {
535    let y = 0;
536    for (let i = 0;i < this.getChildrenCount(); i++) {
537      let child = this.getChild(i);
538      if (child) {
539        child.layout({
540          x: vp2px(100),
541          y: vp2px(this.offsetY)
542        });
543        y += child.getMeasuredSize().height + this.space;
544      }
545    }
546    this.setLayoutPosition(position);
547  }
548
549  onDraw(context: DrawContext) {
550    const canvas = context.canvas;
551    const pen = new drawing.Pen();
552    pen.setStrokeWidth(15);
553    pen.setColor({ alpha: 255, red: 255, green: 0, blue: 0 });
554    canvas.attachPen(pen);
555    canvas.drawRect({  
556      left: 50, 
557      right: this.width + 50, 
558      top: 50, 
559      bottom: this.width + 50,
560    });
561    canvas.detachPen();
562  }
563
564  addWidth() {
565    this.width = (this.width + 10) % 50 + 100;
566  }
567}
568
569class MyNodeController extends NodeController {
570  public rootNode: MyFrameNode | null = null;
571
572  makeNode(context: UIContext): FrameNode | null {
573    this.rootNode = new MyFrameNode(context);
574    this.rootNode?.commonAttribute?.size({ width: 100, height: 100 }).backgroundColor(Color.Green);
575    let frameNode: FrameNode = new FrameNode(context);
576    this.rootNode.appendChild(frameNode);
577    frameNode.commonAttribute.width(10).height(10).backgroundColor(Color.Pink);
578    return this.rootNode;
579  }
580}
581
582@Entry
583@Component
584struct Index {
585  private nodeController: MyNodeController = new MyNodeController();
586
587  build() {
588    Row() {
589      Column() {
590        NodeContainer(this.nodeController)
591          .width('100%')
592          .height(200)
593          .backgroundColor('#FFF0F0F0')
594        Button('Invalidate')
595          .margin(10)
596          .onClick(() => {
597            this.nodeController?.rootNode?.addWidth();
598            this.nodeController?.rootNode?.invalidate();
599          })
600        Button('UpdateLayout')
601          .onClick(() => {
602            let node = this.nodeController.rootNode;
603            node!.offsetY = (node!.offsetY + 10) % 110;
604            this.nodeController?.rootNode?.setNeedsLayout();
605          })
606      }
607      .width('100%')
608      .height('100%')
609    }
610    .height('100%')
611  }
612}
613```
614
615## 查找节点及获取基础信息
616
617FrameNode提供了查询接口用于返回实体节点的基础信息。具体返回的信息内容参考FrameNode中提供的接口。
618
619查找获得FrameNode的方式包括三种:
620
6211. 使用[getFrameNodeById](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyid12)获取。
622
6232. 使用[getFrameNodeByUniqueId](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyuniqueid12)获取。
624
6253. 通过[无感监听](../reference/apis-arkui/js-apis-arkui-observer.md)获取。
626
627> **说明:**
628>
629> 当前接口提供的可查询的信息包括:
630>
631> - 节点大小:[getMeasuredSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getmeasuredsize12),[getUserConfigSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigsize12)
632> 
633> - 布局信息:[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)
634> 
635> - 节点信息:[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)
636