1# FrameNode 2 3## Overview 4 5For third-party frameworks with custom frontend definitions, converting specific DSL to ArkUI declarative descriptions is required. This process, which relies on additional data-driven bindings to the [Builder](../quick-start/arkts-builder.md), is complex and can be performance-intensive. Such frameworks typically leverage ArkUI's layout and event handling, as well as basic node operations and customization capabilities. While most components are customized, some built-in components are needed for mixed display. This is where [FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md) comes into the picture. 6 7FrameNode represents an entity node in the component tree. When used with custom placeholder containers such as [NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md), it allows for mounting and dynamically managing a custom node tree in the containers, including node addition, modification, and removal. Basic FrameNodes enable universal attribute setting, event callback setting, and full customization for measurement, layout, and rendering. 8 9Moreover, the ArkUI framework enables obtaining and traversing proxy FrameNode objects for built-in components, known as proxy nodes, which facilitate UI tree traversal and allow for obtaining specific information about built-in components or registering additional event listeners. 10 11## Creating and Removing Nodes 12 13You can create and remove nodes with **FrameNode**. You can create a custom instance of **FrameNode** using its constructor, and the instance thereby created corresponds to an entity node. You can use the [dispose](../reference/apis-arkui/js-apis-arkui-frameNode.md#dispose12) API in **FrameNode** to break the binding with the entity node. 14 15> **NOTE** 16> 17> - A valid **UIContext** object is required for creating a FrameNode. If no **UIContext** object is provided or if the provided one is invalid, an exception will be thrown during node creation. 18> 19> - Maintain UI context consistency for custom placeholder components to prevent display issues. 20> 21> - **FrameNode** objects are subject to garbage collection (GC) if not retained. 22 23## Checking Whether a Node is Modifiable 24 25Use [isModifiable](../reference/apis-arkui/js-apis-arkui-frameNode.md#ismodifiable12) to check whether the current node is a proxy for a built-in component. If a FrameNode serves as a proxy, it cannot be modified, which means you cannot change its properties or the structure of its child nodes. 26 27## Obtaining the Corresponding RenderNode 28 29Use the [getRenderNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#getrendernode) API to obtain the RenderNode associated with the FrameNode. You can then perform operations on the obtained RenderNode object to dynamically modify the drawing-related properties of the FrameNode. For details about the properties that can be modified, see the RenderNode API documentation. 30 31> **NOTE** 32> 33> - You cannot obtain the RenderNode for a built-in component's proxy FrameNode. 34> 35> - In **BuilderNode**, you can use [getFrameNode](../reference/apis-arkui/js-apis-arkui-builderNode.md#getframenode) to get the FrameNode object, and then use **getRenderNode** to obtain the RenderNode object of the corresponding root node. 36 37## Operating the Node Tree 38 39With **FrameNode**, you can add, delete, query, and modify nodes, thereby changing the subtree structure of non-proxy nodes; you can also query the parent-child relationships to obtain the results. 40 41> **NOTE** 42> 43> Illegal operations for adding, deleting, or modifying nodes result in exceptions. 44> 45> Proxy nodes obtained through queries can only be used to obtain node information and cannot modify node properties; they do not hold the component entity nodes, meaning they do not affect the lifecycle of the corresponding nodes. 46> 47> Node queries only return UI-related nodes and do not include syntax nodes. 48> 49> In scenarios using custom components, you may query and obtain newly added nodes of the custom components, with the node type being **__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("Verify the add, delete, and modify features of the FrameNode") 199 Button("Operate Custom FrameNode") 200 .fontSize(16) 201 .width(400) 202 .onClick(() => { 203 // Add, delete, and modify FrameNode child nodes, which is properly implemented. 204 this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.frameNode); 205 }) 206 Button("Operate Proxy Node in BuilderNode") 207 .fontSize(16) 208 .width(400) 209 .onClick(() => { 210 // Add, delete, and modify the BuilderNode proxy nodes, which results in an exception. 211 this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.buttonNode?.getFrameNode()); 212 }) 213 Button("Operate Proxy Node in Built-in Component") 214 .fontSize(16) 215 .width(400) 216 .onClick(() => { 217 // Add, delete, and modify the proxy nodes, which results in an exception. 218 this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.rootNode?.getParent()); 219 }) 220 } 221 } 222 223 ListItem() { 224 Column({ space: 5 }) { 225 Text("Verify the feature to add subnodes to FrameNode") 226 Button("Add BuilderNode Proxy Node") 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("Add Built-in Component Proxy Node") 235 .fontSize(16) 236 .width(400) 237 .onClick(() => { 238 this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, this.myNodeController?.rootNode?.getParent()); 239 }) 240 Button("Add Custom Node with Existing Parent Node") 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("Verify the query feature of the FrameNode") 252 Button("Operate Custom FrameNode") 253 .fontSize(16) 254 .width(400) 255 .onClick(() => { 256 // Query the FrameNode. The current node is a child of the NodeContainer. 257 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode); 258 setTimeout(() => { 259 // Query the FrameNode. The current node is the first child node under rootNode. 260 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.frameNode); 261 }, 2000) 262 }) 263 Button("Operate Proxy Node in BuilderNode") 264 .fontSize(16) 265 .width(400) 266 .onClick(() => { 267 // Query the BuilderNode proxy nodes. The current node is the Column node within BuilderNode. 268 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.buttonNode?.getFrameNode()); 269 }) 270 Button("Operate Proxy Node in Built-in Component") 271 .fontSize(16) 272 .width(400) 273 .onClick(() => { 274 // Query the proxy node. The current node is the 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## Setting Universal Attributes and Event Callbacks 308 309Use the [commonAttribute](../reference/apis-arkui/js-apis-arkui-frameNode.md#commonattribute12) and [commonEvent](../reference/apis-arkui/js-apis-arkui-frameNode.md#commonevent12) objects to set the [universal attributes](../reference/apis-arkui/arkui-ts/ts-universal-attributes-size.md) and [event callbacks](../reference/apis-arkui/arkui-ts/ts-uicommonevent.md), respectively. 310 311> **NOTE** 312> 313> - Proxy node attributes are immutable. Therefore, **commonAttribute** is ineffective on proxy nodes. 314> 315> - The custom basic events that you define run in parallel with the events predefined in the built-in components, without overriding them. When two event callbacks are set, the built-in component event callback is prioritized. 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 // Modify the attributes of rootNode, which is a custom FrameNode, and the changes take effect. 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 // Modify the attributes of frameNode, which is a custom FrameNode, and the changes take effect. 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 // Modify the attributes of the FrameNode obtained from BuilderNode, which is not a custom FrameNode, and the changes do not take effect. 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("Modify the universal node attributes: width and height.") 397 Button("modify ArkTS-FrameNode") 398 .onClick(() => { 399 // The object obtained is the FrameNode created on the current page, which can be modified. That is, the size and position of the node can be changed. 400 console.log("Check whether 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 // The object obtained is the root node of the BuilderNode on the current page, which cannot be modified. That is, the size and position of the node remain unchanged. 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 // The rootNode object calling getParent() obtains the NodeContainer node on the current page, which cannot be modified. That is, the size and position of the node remain unchanged. 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("Modify the node click event.") 434 Button("add click event to ArkTS-FrameNode") 435 .onClick(() => { 436 // The object obtained is the FrameNode created on the current page, to which click events can be added. 437 // The added click event participates in event competition, meaning the click event will be consumed by this node and will no longer bubble up to the parent component. 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 // The object obtained is the root node of the BuilderNode on the current page, to which click events can be added. 445 // When the button is clicked, the click event callback set through the built-in component API is called first, followed by the click listener added through commonEvent. 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 // The rootNode object calling getParent() obtains the NodeContainer node on the current page, to which click events can be added. 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## Implementing Custom Measurement, Layout, and Drawing 475 476By overriding the [onDraw](../reference/apis-arkui/js-apis-arkui-frameNode.md#ondraw12) API, you can customize the drawing content of the FrameNode. Use the [invalidate](../reference/apis-arkui/js-apis-arkui-frameNode.md#invalidate12) API to manually trigger a redraw of the node. 477 478By overriding the [onMeasure](../reference/apis-arkui/js-apis-arkui-frameNode.md#onmeasure12) API, you can customize how the FrameNode measures its size. Use [measure](../reference/apis-arkui/js-apis-arkui-frameNode.md#measure12) to proactively pass layout constraints to initiate a remeasurement. 479 480By overriding the [onLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#onlayout12) API, you can customize the layout of the FrameNode. Use [layout](../reference/apis-arkui/js-apis-arkui-frameNode.md#layout12) to proactively pass position information and initiate a re-layout. 481 482Use [setNeedsLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#setneedslayout12) to mark the current node and trigger a re-layout in the next frame. 483 484> **NOTE** 485> 486> - After a node is disposed and unbound, the FrameNode no longer represents an entity node. In this case, the **invalidate** call cannot update the previously bound node. 487> 488> - Custom drawings made through the **onDraw** API cannot exceed the component's size. 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## Searching for Nodes and Obtaining Basic Information 616 617**FrameNode** provides APIs for obtaining basic information about an entity node. For details about the returned information, see the FrameNode API documentation. 618 619To obtain a FrameNode, use any of the following methods: 620 6211. Use [getFrameNodeById](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyid12). 622 6232. Use [getFrameNodeByUniqueId](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyuniqueid12). 624 6253. Use an [observer](../reference/apis-arkui/js-apis-arkui-observer.md). 626 627> **NOTE** 628> 629> Currently, the following information can be obtained: 630> 631> - Node size: **getMeasuredSize**, **getUserConfigSize** 632> 633> - Layout information: **getPositionToWindow**, **getPositionToParent**, **getLayoutPosition**, **getUserConfigBorderWidth**, **getUserConfigPadding**, **getUserConfigMargin** 634> 635> - Node information: **getId**, **getUniqueId**, **getNodeType**, **getOpacity**, **isVisible**, **isClipToFrame**, **isAttached**, **getInspectorInfo**, **getCustomProperty** 636