1# Custom Component Node (FrameNode) 2 3## Overview 4 5For third-party frameworks with custom frontend definitions, such as those in JSON, XML, or a DOM tree, a conversion from the specific DSL into ArkUI's declarative descriptions is necessary. The following figure shows the mapping between a frontend framework defined by JSON and the ArkUI declarative description. 6 7 8 9The aforementioned conversion process, which relies on additional data-driven bindings to the [Builder](../ui/state-management/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. In the example shown below, while the custom method of **FrameNode** is used for drawing, a built-in component **Column** and its child component **Text** are mounted to the root FrameNode through **BuilderNode**, thereby achieving mixed display. 10 11 12 13This is where [FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md) comes into the picture. **FrameNode** represents an entity node in the component tree. It is used in conjunction with the custom placeholder container component, [NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md), to construct a custom node tree inside the placeholder container. This node tree supports dynamic operations, including node addition, modification, and removal. Basic FrameNodes provide functionality for setting universal attributes and event callbacks, as well as comprehensive customization capabilities for measurement, layout, and rendering. 14 15Moreover, 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. 16 17## Creating and Removing Nodes 18 19You 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. 20 21> **NOTE** 22> 23> - 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. 24> 25> - Maintain UI context consistency for custom placeholder components to prevent display issues. 26> 27> - **FrameNode** objects are subject to garbage collection (GC) if not retained. 28 29## Checking Whether a Node is Modifiable 30 31Use [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. 32 33## Obtaining the Corresponding RenderNode 34 35Use 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 [RenderNode](arkts-user-defined-arktsNode-renderNode.md). 36 37> **NOTE** 38> 39> - You cannot obtain the RenderNode for a built-in component's proxy FrameNode. 40> 41> - 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. 42 43## Operating the Node Tree 44 45With **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. 46 47> **NOTE** 48> 49> Illegal operations for adding, deleting, or modifying nodes will result in exceptions. 50> 51> Proxy nodes obtained through queries are read-only. They can be used to obtain node information, but cannot modify node properties. These proxy nodes do not hold references to the underlying component entity nodes and therefore do not affect the lifecycle of the corresponding nodes. 52> 53> Node queries only return UI-related nodes and do not include syntax nodes. 54> 55> In scenarios using custom components, you may query and obtain newly added nodes of the custom components, with the node type being **\_\_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("Verify the add, delete, and modify features of the FrameNode") 210 Button("Operate Custom FrameNode") 211 .fontSize(16) 212 .width(400) 213 .onClick(() => { 214 // Add, delete, and modify FrameNode child nodes, which is properly implemented. 215 this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.frameNode); 216 }) 217 Button("Operate Proxy Node in BuilderNode") 218 .fontSize(16) 219 .width(400) 220 .onClick(() => { 221 // Add, delete, and modify the BuilderNode proxy node to generate an exception. 222 this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.buttonNode?.getFrameNode()); 223 }) 224 Button("Operate Proxy Node in Built-in Component") 225 .fontSize(16) 226 .width(400) 227 .onClick(() => { 228 // Add, delete, and modify the proxy node to generate an exception. 229 this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.rootNode?.getParent()); 230 }) 231 } 232 } 233 234 ListItem() { 235 Column({ space: 5 }) { 236 Text("Verify the feature to add subnodes to FrameNode") 237 Button("Add BuilderNode Proxy Node") 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("Add Built-in Component Proxy Node") 246 .fontSize(16) 247 .width(400) 248 .onClick(() => { 249 this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, 250 this.myNodeController?.rootNode?.getParent()); 251 }) 252 Button("Add Custom Node with Existing Parent Node") 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("Verify the query feature of the FrameNode") 265 Button("Operate Custom FrameNode") 266 .fontSize(16) 267 .width(400) 268 .onClick(() => { 269 // Query the FrameNode. The current node is a child of the NodeContainer. 270 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode); 271 setTimeout(() => { 272 // Query the FrameNode. The current node is the first child node under rootNode. 273 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.frameNode); 274 }, 2000) 275 }) 276 Button("Operate Proxy Node in BuilderNode") 277 .fontSize(16) 278 .width(400) 279 .onClick(() => { 280 // Query the BuilderNode proxy nodes. The current node is the Column node within BuilderNode. 281 this.result = 282 this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.buttonNode?.getFrameNode()); 283 }) 284 Button("Operate Proxy Node in Built-in Component") 285 .fontSize(16) 286 .width(400) 287 .onClick(() => { 288 // Query the proxy node. The current node is the 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## Setting Universal Attributes and Event Callbacks 328 329Use 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. 330 331> **NOTE** 332> 333> - Proxy node attributes are immutable. Therefore, **commonAttribute** is ineffective on proxy nodes. 334> 335> - 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. 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 // Modify the attributes of rootNode, which is a custom FrameNode, and the changes take effect. 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 // Modify the attributes of frameNode, which is a custom FrameNode, and the changes take effect. 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 // Modify the attributes of the FrameNode obtained from BuilderNode, which is not a custom FrameNode, and the changes do not take effect. 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("Modify the universal node attributes: width and height.") 417 Button("modify ArkTS-FrameNode") 418 .onClick(() => { 419 // 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. 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 // 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. 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 // 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. 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("Modify the node click event.") 461 Button("add click event to ArkTS-FrameNode") 462 .onClick(() => { 463 // The object obtained is the FrameNode created on the current page, to which click events can be added. 464 // 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. 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 // The object obtained is the root node of the BuilderNode on the current page, to which click events can be added. 472 // 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. 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 // The rootNode object calling getParent() obtains the NodeContainer node on the current page, to which click events can be added. 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## Implementing Custom Measurement, Layout, and Drawing 514 515By 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. 516 517By 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. 518 519By 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. 520 521Use [setNeedsLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#setneedslayout12) to mark the current node and trigger a re-layout in the next frame. 522 523> **NOTE** 524> 525> - 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. 526> 527> - Custom drawings made through the **onDraw** API cannot exceed the component's size. 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## Searching for Nodes and Obtaining Basic Information 660 661**FrameNode** provides APIs for obtaining basic information about an entity node. For details about the returned information, see the FrameNode API documentation. 662 663To obtain a FrameNode, use any of the following methods: 664 6651. Use [getFrameNodeById](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyid12). 666 6672. Use [getFrameNodeByUniqueId](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyuniqueid12). 668 6693. Use an [observer](../reference/apis-arkui/js-apis-arkui-observer.md). 670 671> **NOTE** 672> 673> 1. Currently, the following information can be obtained: 674> 675> - Node size: [getMeasuredSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getmeasuredsize12), [getUserConfigSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigsize12) 676> 677> - Layout information: [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> - Node information: [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-type nodes, such as JsView nodes, [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), and **if/else** components, cannot be obtained. 682 683## Obtaining Node Position Offset Information 684 685**FrameNode** provides APIs to obtain the position offsets of nodes relative to the window, parent component, and the screen: [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(); // Obtain the position offset of the FrameNode relative to the window. 705 console.log(`${TEST_TAG} ${JSON.stringify(positionToWindow)}`); 706 } 707 708 getPositionToParent() { 709 let positionToParent = this.rootNode?.getPositionToParent(); // Obtain the position offset of the FrameNode relative to the parent component. 710 console.log(`${TEST_TAG} ${JSON.stringify(positionToParent)}`); 711 } 712 713 getPositionToScreen() { 714 let positionToScreen = this.rootNode?.getPositionToScreen(); // Obtain the position offset of the FrameNode relative to the screen. 715 console.log(`${TEST_TAG} ${JSON.stringify(positionToScreen)}`); 716 } 717 718 getPositionToWindowWithTransform() { 719 let positionToWindowWithTransform = 720 this.rootNode?.getPositionToWindowWithTransform(); // Obtain the position offset of the FrameNode relative to the window with drawing attributes. 721 console.log(`${TEST_TAG} ${JSON.stringify(positionToWindowWithTransform)}`); 722 } 723 724 getPositionToParentWithTransform() { 725 let positionToParentWithTransform = 726 this.rootNode?.getPositionToParentWithTransform(); // Obtain the position offset of the FrameNode relative to the parent component with drawing attributes. 727 console.log(`${TEST_TAG} ${JSON.stringify(positionToParentWithTransform)}`); 728 } 729 730 getPositionToScreenWithTransform() { 731 let positionToScreenWithTransform = 732 this.rootNode?.getPositionToScreenWithTransform(); // Obtain the position offset of the FrameNode relative to the screen with drawing attributes. 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## Creating a FrameNode of a Specific Type Using typeNode 800 801By creating a FrameNode of a specific type using **typeNode**, you can obtain user-set attribute information through attribute obtaining APIs. 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(); // Obtain the border width set by the user. 871 console.log(`${TEST_TAG} ${JSON.stringify(userConfigBorderWidth)}`); 872 } 873 874 getUserConfigPadding(frameNode: FrameNode) { 875 let userConfigPadding = frameNode?.getUserConfigPadding(); // Obtain the padding set by the user. 876 console.log(`${TEST_TAG} ${JSON.stringify(userConfigPadding)}`); 877 } 878 879 getUserConfigMargin(frameNode: FrameNode) { 880 let userConfigMargin = frameNode?.getUserConfigMargin(); // Obtain the margin set by the user. 881 console.log(`${TEST_TAG} ${JSON.stringify(userConfigMargin)}`); 882 } 883 884 getUserConfigSize(frameNode: FrameNode) { 885 let userConfigSize = frameNode?.getUserConfigSize(); // Obtain the width and height set by the user. 886 console.log(`${TEST_TAG} ${JSON.stringify(userConfigSize)}`); 887 } 888 889 getId(frameNode: FrameNode) { 890 let id = frameNode?.getId(); // Obtain the node ID set by the user. 891 console.log(`${TEST_TAG} ${id}`); 892 } 893 894 getUniqueId(frameNode: FrameNode) { 895 let uniqueId = frameNode?.getUniqueId(); // Obtain the unique node ID allocated by the system. 896 console.log(`${TEST_TAG} ${uniqueId}`); 897 } 898 899 getNodeType(frameNode: FrameNode) { 900 let nodeType = frameNode?.getNodeType(); // Obtain the node type. 901 console.log(`${TEST_TAG} ${nodeType}`); 902 } 903 904 getOpacity(frameNode: FrameNode) { 905 let opacity = frameNode?.getOpacity(); // Obtain the node opacity. 906 console.log(`${TEST_TAG} ${JSON.stringify(opacity)}`); 907 } 908 909 isVisible(frameNode: FrameNode) { 910 let visible = frameNode?.isVisible(); // Obtain whether the node is visible. 911 console.log(`${TEST_TAG} ${JSON.stringify(visible)}`); 912 } 913 914 isClipToFrame(frameNode: FrameNode) { 915 let clipToFrame = frameNode?.isClipToFrame(); // Obtain whether the node is clipped to the component area. 916 console.log(`${TEST_TAG} ${JSON.stringify(clipToFrame)}`); 917 } 918 919 isAttached(frameNode: FrameNode) { 920 let attached = frameNode?.isAttached(); // Obtain whether a node is mounted to the main node tree. 921 console.log(`${TEST_TAG} ${JSON.stringify(attached)}`); 922 } 923 924 getInspectorInfo(frameNode: FrameNode) { 925 let inspectorInfo = frameNode?.getInspectorInfo(); // Obtain the structure information of the node. 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## Disassociating the Current FrameNode Object from the Entity FrameNode 1121 1122To disassociate the current **FrameNode** object from the entity FrameNode, call the [dispose](../reference/apis-arkui/js-apis-arkui-frameNode.md#dispose12) API. 1123 1124> **NOTE** 1125> 1126> After the **dispose** API is called, the **FrameNode** object no longer corresponds to any actual FrameNode. In this case, any attempt to call the following APIs will result in a JS crash in the application: **getMeasuredSize**, **getLayoutPosition**, **getUserConfigBorderWidth**, **getUserConfigPadding**, **getUserConfigMargin**, **getUserConfigSize**. 1127> 1128> To check whether the current **FrameNode** object corresponds to an entity FrameNode, you can use [getUniqueId](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuniqueid12) API. A **UniqueId** value greater than 0 indicates that the object is associated with an entity 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## Using the Lazy Loading Capability of FrameNode 1231 1232To implement lazy loading for custom nodes, you can use the [NodeAdapter](../reference/apis-arkui/js-apis-arkui-frameNode.md#nodeadapter12) object, the counterpart of **LazyForEach** on ArkTS. 1233 1234> **NOTE** 1235> 1236> Make sure the input parameter is not a negative number. If a negative value is provided, no action will be taken. 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