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](../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. 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 13FrameNode 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 a custom node tree in the containers. The node tree supports dynamic operations, including node addition, modification, and removal. Basic FrameNodes enable universal attribute setting, event callback setting, and full customization 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 result in exceptions. 50> 51> 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. 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 + " : " + (err as BusinessError).message); 140 } 141 try { 142 frameNode?.insertChildAfter(frameNode2, null); 143 console.log(TEST_TAG + " insertChildAfter success "); 144 } catch (err) { 145 console.log(TEST_TAG + " insertChildAfter fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message); 146 } 147 setTimeout(() => { 148 try { 149 frameNode?.removeChild(frameNode?.getChild(0)) 150 console.log(TEST_TAG + " removeChild success "); 151 } catch (err) { 152 console.log(TEST_TAG + " removeChild fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message); 153 } 154 }, 2000) 155 setTimeout(() => { 156 try { 157 frameNode?.clearChildren(); 158 console.log(TEST_TAG + " clearChildren success "); 159 } catch (err) { 160 console.log(TEST_TAG + " clearChildren fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message); 161 } 162 }, 4000) 163 } 164 } 165 166 testInterfaceAboutSearch(frameNode: FrameNode | undefined | null): string { 167 let result: string = ""; 168 if (frameNode) { 169 result = result + `current node is ${frameNode.getNodeType()} \n`; 170 result = result + `parent node is ${frameNode.getParent()?.getNodeType()} \n`; 171 result = result + `child count is ${frameNode.getChildrenCount()} \n`; 172 result = result + `first child node is ${frameNode.getFirstChild()?.getNodeType()} \n`; 173 result = result + `second child node is ${frameNode.getChild(1)?.getNodeType()} \n`; 174 result = result + `previousSibling node is ${frameNode.getPreviousSibling()?.getNodeType()} \n`; 175 result = result + `nextSibling node is ${frameNode.getNextSibling()?.getNodeType()} \n`; 176 } 177 return result; 178 } 179 180 checkAppendChild(parent: FrameNode | undefined | null, child: FrameNode | undefined | null) { 181 try { 182 if (parent && child) { 183 parent.appendChild(child); 184 console.log(TEST_TAG + " appendChild success "); 185 } 186 } catch (err) { 187 console.log(TEST_TAG + " appendChild fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message); 188 } 189 } 190} 191 192@Entry 193@Component 194struct Index { 195 @State index: number = 0; 196 @State result: string = "" 197 private myNodeController: MyNodeController = new MyNodeController(); 198 199 build() { 200 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { 201 List({ space: 20, initialIndex: 0 }) { 202 ListItem() { 203 Column({ space: 5 }) { 204 Text("Verify the add, delete, and modify features of the FrameNode") 205 Button("Operate Custom FrameNode") 206 .fontSize(16) 207 .width(400) 208 .onClick(() => { 209 // Add, delete, and modify FrameNode child nodes, which is properly implemented. 210 this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.frameNode); 211 }) 212 Button("Operate Proxy Node in BuilderNode") 213 .fontSize(16) 214 .width(400) 215 .onClick(() => { 216 // Add, delete, and modify the BuilderNode proxy node to generate an exception. 217 this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.buttonNode?.getFrameNode()); 218 }) 219 Button("Operate Proxy Node in Built-in Component") 220 .fontSize(16) 221 .width(400) 222 .onClick(() => { 223 // Add, delete, and modify the proxy node to generate an exception. 224 this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.rootNode?.getParent()); 225 }) 226 } 227 } 228 229 ListItem() { 230 Column({ space: 5 }) { 231 Text("Verify the feature to add subnodes to FrameNode") 232 Button("Add BuilderNode Proxy Node") 233 .fontSize(16) 234 .width(400) 235 .onClick(() => { 236 let buttonNode = new BuilderNode<[Params]>(this.getUIContext()); 237 buttonNode.build(wrapBuilder<[Params]>(buttonBuilder), { text: "BUTTON" }) 238 this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, buttonNode?.getFrameNode()); 239 }) 240 Button("Add Built-in Component Proxy Node") 241 .fontSize(16) 242 .width(400) 243 .onClick(() => { 244 this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, this.myNodeController?.rootNode?.getParent()); 245 }) 246 Button("Add Custom Node with Existing Parent Node") 247 .fontSize(16) 248 .width(400) 249 .onClick(() => { 250 this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, this.myNodeController?.rootNode); 251 }) 252 } 253 } 254 255 ListItem() { 256 Column({ space: 5 }) { 257 Text("Verify the query feature of the FrameNode") 258 Button("Operate Custom FrameNode") 259 .fontSize(16) 260 .width(400) 261 .onClick(() => { 262 // Query the FrameNode. The current node is a child of the NodeContainer. 263 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode); 264 setTimeout(() => { 265 // Query the FrameNode. The current node is the first child node under rootNode. 266 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.frameNode); 267 }, 2000) 268 }) 269 Button("Operate Proxy Node in BuilderNode") 270 .fontSize(16) 271 .width(400) 272 .onClick(() => { 273 // Query the BuilderNode proxy nodes. The current node is the Column node within BuilderNode. 274 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.buttonNode?.getFrameNode()); 275 }) 276 Button("Operate Proxy Node in Built-in Component") 277 .fontSize(16) 278 .width(400) 279 .onClick(() => { 280 // Query the proxy node. The current node is the NodeContainer. 281 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode?.getParent()); 282 }) 283 } 284 } 285 }.height("50%") 286 287 Text(`Result: \n${this.result}`) 288 .fontSize(16) 289 .width(400) 290 .height(200) 291 .padding(30) 292 .borderWidth(1) 293 Column() { 294 Text("This is a NodeContainer.") 295 .textAlign(TextAlign.Center) 296 .borderRadius(10) 297 .backgroundColor(0xFFFFFF) 298 .width('100%') 299 .fontSize(16) 300 NodeContainer(this.myNodeController) 301 .borderWidth(1) 302 .width(400) 303 .height(150) 304 } 305 } 306 .padding({ left: 35, right: 35, top: 35, bottom: 35 }) 307 .width("100%") 308 .height("100%") 309 } 310} 311``` 312 313## Setting Universal Attributes and Event Callbacks 314 315Use 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. 316 317> **NOTE** 318> 319> - Proxy node attributes are immutable. Therefore, **commonAttribute** is ineffective on proxy nodes. 320> 321> - 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. 322 323```ts 324import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI' 325 326class Params { 327 text: string = "this is a text" 328} 329 330@Builder 331function buttonBuilder(params: Params) { 332 Button(params.text) 333 .fontSize(12) 334 .borderRadius(8) 335 .borderWidth(2) 336 .backgroundColor(Color.Orange) 337 .onClick((event: ClickEvent) => { 338 console.log(`Button ${JSON.stringify(event)}`); 339 }) 340} 341 342class MyNodeController extends NodeController { 343 public buttonNode: BuilderNode<[Params]> | null = null; 344 public frameNode: FrameNode | null = null; 345 public rootNode: FrameNode | null = null; 346 private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(buttonBuilder); 347 348 makeNode(uiContext: UIContext): FrameNode | null { 349 if (this.rootNode == null) { 350 this.rootNode = new FrameNode(uiContext); 351 // Modify the attributes of rootNode, which is a custom FrameNode, and the changes take effect. 352 this.rootNode.commonAttribute 353 .width("100%") 354 .height(100) 355 .borderWidth(1) 356 .backgroundColor(Color.Gray) 357 } 358 359 if (this.frameNode == null) { 360 this.frameNode = new FrameNode(uiContext); 361 // Modify the attributes of frameNode, which is a custom FrameNode, and the changes take effect. 362 this.frameNode.commonAttribute 363 .width("50%") 364 .height(50) 365 .borderWidth(1) 366 .backgroundColor(Color.Pink); 367 this.rootNode.appendChild(this.frameNode); 368 } 369 if (this.buttonNode == null) { 370 this.buttonNode = new BuilderNode<[Params]>(uiContext); 371 this.buttonNode.build(this.wrapBuilder, { text: "This is a Button" }) 372 // Modify the attributes of the FrameNode obtained from BuilderNode, which is not a custom FrameNode, and the changes do not take effect. 373 this.buttonNode?.getFrameNode()?.commonAttribute.position({ x: 100, y: 100 }) 374 this.rootNode.appendChild(this.buttonNode.getFrameNode()) 375 } 376 return this.rootNode; 377 } 378 379 modifyNode(frameNode: FrameNode | null | undefined, sizeValue: SizeOptions, positionValue: Position) { 380 if (frameNode) { 381 frameNode.commonAttribute.size(sizeValue).position(positionValue); 382 } 383 } 384 385 addClickEvent(frameNode: FrameNode | null | undefined) { 386 if (frameNode) { 387 frameNode.commonEvent.setOnClick((event: ClickEvent) => { 388 console.log(`FrameNode ${JSON.stringify(event)}`); 389 }) 390 } 391 } 392} 393 394@Entry 395@Component 396struct Index { 397 private myNodeController: MyNodeController = new MyNodeController(); 398 399 build() { 400 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { 401 Column({ space: 10 }) { 402 Text("Modify the universal node attributes: width and height.") 403 Button("modify ArkTS-FrameNode") 404 .onClick(() => { 405 // 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. 406 console.log("Check whether the node can be modified " + this.myNodeController?.frameNode 407 ?.isModifiable()); 408 this.myNodeController.modifyNode(this.myNodeController?.frameNode, { width: 150, height: 100 }, { 409 x: 100, 410 y: 0 411 }) 412 }) 413 Button("modify FrameNode get by BuilderNode") 414 .onClick(() => { 415 // 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. 416 console.log("Check the weather the node can be modified " + this.myNodeController?.buttonNode?.getFrameNode() 417 ?.isModifiable()); 418 this.myNodeController.modifyNode(this.myNodeController?.buttonNode?.getFrameNode(), { 419 width: 100, 420 height: 100 421 }, { x: 50, y: 50 }) 422 }) 423 Button("modify proxyFrameNode get by search") 424 .onClick(() => { 425 // 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. 426 console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent() 427 ?.isModifiable()); 428 this.myNodeController.modifyNode(this.myNodeController?.rootNode?.getParent(), { 429 width: 500, 430 height: 500 431 }, { 432 x: 0, 433 y: 0 434 }) 435 }) 436 }.padding({ left: 35, right: 35, top: 35, bottom: 35 }) 437 438 Column({ space: 10 }) { 439 Text("Modify the node click event.") 440 Button("add click event to ArkTS-FrameNode") 441 .onClick(() => { 442 // The object obtained is the FrameNode created on the current page, to which click events can be added. 443 // 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. 444 console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent() 445 ?.isModifiable()); 446 this.myNodeController.addClickEvent(this.myNodeController?.frameNode) 447 }) 448 Button("add click event to FrameNode get by BuilderNode") 449 .onClick(() => { 450 // The object obtained is the root node of the BuilderNode on the current page, to which click events can be added. 451 // 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. 452 console.log("Check the weather the node can be modified " + this.myNodeController?.buttonNode?.getFrameNode() 453 ?.isModifiable()); 454 this.myNodeController.addClickEvent(this.myNodeController?.buttonNode?.getFrameNode()) 455 }) 456 Button("add click event to proxyFrameNode get by search") 457 .onClick(() => { 458 // The rootNode object calling getParent() obtains the NodeContainer node on the current page, to which click events can be added. 459 console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent() 460 ?.isModifiable()); 461 this.myNodeController.addClickEvent(this.myNodeController?.rootNode?.getParent()); 462 }) 463 }.padding({ left: 35, right: 35, top: 35, bottom: 35 }) 464 465 NodeContainer(this.myNodeController) 466 .borderWidth(1) 467 .width("100%") 468 .height(100) 469 .onClick((event: ClickEvent) => { 470 console.log(`NodeContainer ${JSON.stringify(event)}`); 471 }) 472 } 473 .padding({ left: 35, right: 35, top: 35, bottom: 35 }) 474 .width("100%") 475 .height("100%") 476 } 477} 478``` 479 480## Implementing Custom Measurement, Layout, and Drawing 481 482By 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. 483 484By 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. 485 486By 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. 487 488Use [setNeedsLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#setneedslayout12) to mark the current node and trigger a re-layout in the next frame. 489 490> **NOTE** 491> 492> - 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. 493> 494> - Custom drawings made through the **onDraw** API cannot exceed the component's size. 495 496```ts 497import { DrawContext, FrameNode, NodeController, Position, Size, UIContext, LayoutConstraint } from '@kit.ArkUI'; 498import { drawing } from '@kit.ArkGraphics2D'; 499 500function GetChildLayoutConstraint(constraint: LayoutConstraint, child: FrameNode): LayoutConstraint { 501 const size = child.getUserConfigSize(); 502 const width = Math.max( 503 Math.min(constraint.maxSize.width, size.width.value), 504 constraint.minSize.width 505 ); 506 const height = Math.max( 507 Math.min(constraint.maxSize.height, size.height.value), 508 constraint.minSize.height 509 ); 510 const finalSize: Size = { width, height }; 511 const res: LayoutConstraint = { 512 maxSize: finalSize, 513 minSize: finalSize, 514 percentReference: finalSize 515 }; 516 517 return res; 518} 519 520class MyFrameNode extends FrameNode { 521 public width: number = 100; 522 public offsetY: number = 0; 523 private space: number = 1; 524 525 onMeasure(constraint: LayoutConstraint): void { 526 let sizeRes: Size = { width: vp2px(100), height: vp2px(100) }; 527 for (let i = 0;i < this.getChildrenCount(); i++) { 528 let child = this.getChild(i); 529 if (child) { 530 let childConstraint = GetChildLayoutConstraint(constraint, child); 531 child.measure(childConstraint); 532 let size = child.getMeasuredSize(); 533 sizeRes.height += size.height + this.space; 534 sizeRes.width = Math.max(sizeRes.width, size.width); 535 } 536 } 537 this.setMeasuredSize(sizeRes); 538 } 539 540 onLayout(position: Position): void { 541 for (let i = 0;i < this.getChildrenCount(); i++) { 542 let child = this.getChild(i); 543 if (child) { 544 child.layout({ 545 x: vp2px(100), 546 y: vp2px(this.offsetY) 547 }); 548 let layoutPosition = child.getLayoutPosition(); 549 console.log("child position:" + JSON.stringify(layoutPosition)); 550 } 551 } 552 this.setLayoutPosition(position); 553 } 554 555 onDraw(context: DrawContext) { 556 const canvas = context.canvas; 557 const pen = new drawing.Pen(); 558 pen.setStrokeWidth(15); 559 pen.setColor({ alpha: 255, red: 255, green: 0, blue: 0 }); 560 canvas.attachPen(pen); 561 canvas.drawRect({ 562 left: 50, 563 right: this.width + 50, 564 top: 50, 565 bottom: this.width + 50, 566 }); 567 canvas.detachPen(); 568 } 569 570 addWidth() { 571 this.width = (this.width + 10) % 50 + 100; 572 } 573} 574 575class MyNodeController extends NodeController { 576 public rootNode: MyFrameNode | null = null; 577 578 makeNode(context: UIContext): FrameNode | null { 579 this.rootNode = new MyFrameNode(context); 580 this.rootNode?.commonAttribute?.size({ width: 100, height: 100 }).backgroundColor(Color.Green); 581 let frameNode: FrameNode = new FrameNode(context); 582 this.rootNode.appendChild(frameNode); 583 frameNode.commonAttribute.width(10).height(10).backgroundColor(Color.Pink); 584 return this.rootNode; 585 } 586} 587 588@Entry 589@Component 590struct Index { 591 private nodeController: MyNodeController = new MyNodeController(); 592 593 build() { 594 Row() { 595 Column() { 596 NodeContainer(this.nodeController) 597 .width('100%') 598 .height(200) 599 .backgroundColor('#FFF0F0F0') 600 Button('Invalidate') 601 .margin(10) 602 .onClick(() => { 603 this.nodeController?.rootNode?.addWidth(); 604 this.nodeController?.rootNode?.invalidate(); 605 }) 606 Button('UpdateLayout') 607 .onClick(() => { 608 let node = this.nodeController.rootNode; 609 node!.offsetY = (node!.offsetY + 10) % 110; 610 this.nodeController?.rootNode?.setNeedsLayout(); 611 }) 612 } 613 .width('100%') 614 .height('100%') 615 } 616 .height('100%') 617 } 618} 619``` 620 621## Searching for Nodes and Obtaining Basic Information 622 623**FrameNode** provides APIs for obtaining basic information about an entity node. For details about the returned information, see the FrameNode API documentation. 624 625To obtain a FrameNode, use any of the following methods: 626 6271. Use [getFrameNodeById](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyid12). 628 6292. Use [getFrameNodeByUniqueId](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyuniqueid12). 630 6313. Use an [observer](../reference/apis-arkui/js-apis-arkui-observer.md). 632 633> **NOTE** 634> 635> 1. Currently, the following information can be obtained: 636> 637> - Node size: [getMeasuredSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getmeasuredsize12), [getUserConfigSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigsize12) 638> 639> - 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) 640> 641> - 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) 642> 643> 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. 644 645## Obtaining Node Position Offset Information 646 647**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). 648 649```ts 650import { NodeController, FrameNode, UIContext } from '@kit.ArkUI'; 651 652const TEST_TAG : string = "FrameNode" 653class MyNodeController extends NodeController { 654 public frameNode: FrameNode | null = null; 655 private rootNode: FrameNode | null = null; 656 makeNode(uiContext: UIContext): FrameNode | null { 657 this.rootNode = new FrameNode(uiContext); 658 this.frameNode = new FrameNode(uiContext); 659 this.rootNode.appendChild(this.frameNode); 660 return this.rootNode; 661 } 662 getPositionToWindow() 663 { 664 let positionToWindow = this.rootNode?.getPositionToWindow(); // Obtain the position offset of the FrameNode relative to the window. 665 console.log(TEST_TAG + JSON.stringify(positionToWindow)); 666 } 667 getPositionToParent() 668 { 669 let positionToParent = this.rootNode?.getPositionToParent(); // Obtain the position offset of the FrameNode relative to the parent component. 670 console.log(TEST_TAG + JSON.stringify(positionToParent)); 671 } 672 getPositionToScreen() 673 { 674 let positionToScreen = this.rootNode?.getPositionToScreen(); // Obtain the position offset of the FrameNode relative to the screen. 675 console.log(TEST_TAG + JSON.stringify(positionToScreen)); 676 } 677 getPositionToWindowWithTransform() 678 { 679 let positionToWindowWithTransform = this.rootNode?.getPositionToWindowWithTransform(); // Obtain the position offset of the FrameNode relative to the window with drawing attributes. 680 console.log(TEST_TAG + JSON.stringify(positionToWindowWithTransform)); 681 } 682 getPositionToParentWithTransform() 683 { 684 let positionToParentWithTransform = this.rootNode?.getPositionToParentWithTransform(); // Obtain the position offset of the FrameNode relative to the parent component with drawing attributes. 685 console.log(TEST_TAG + JSON.stringify(positionToParentWithTransform)); 686 } 687 getPositionToScreenWithTransform() 688 { 689 let positionToScreenWithTransform = this.rootNode?.getPositionToScreenWithTransform(); // Obtain the position offset of the FrameNode relative to the screen with drawing attributes. 690 console.log(TEST_TAG + JSON.stringify(positionToScreenWithTransform)); 691 } 692} 693 694@Entry 695@Component 696struct Index { 697 private myNodeController: MyNodeController = new MyNodeController(); 698 build() { 699 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { 700 Button("getPositionToWindow") 701 .width(300) 702 .onClick(()=>{ 703 this.myNodeController.getPositionToWindow(); 704 }) 705 Button("getPositionToParent") 706 .width(300) 707 .onClick(()=>{ 708 this.myNodeController.getPositionToParent(); 709 }) 710 Button("getPositionToScreen") 711 .width(300) 712 .onClick(()=>{ 713 this.myNodeController.getPositionToScreen(); 714 }) 715 Button("getPositionToParentWithTransform") 716 .width(300) 717 .onClick(()=>{ 718 this.myNodeController.getPositionToParentWithTransform(); 719 }) 720 Button("getPositionToWindowWithTransform") 721 .width(300) 722 .onClick(()=>{ 723 this.myNodeController.getPositionToWindowWithTransform(); 724 }) 725 Button("getPositionToScreenWithTransform") 726 .width(300) 727 .onClick(()=>{ 728 this.myNodeController.getPositionToScreenWithTransform(); 729 }) 730 Column(){ 731 Text("This is a NodeContainer.") 732 .textAlign(TextAlign.Center).borderRadius(10).backgroundColor(0xFFFFFF) 733 .width('100%').fontSize(16) 734 NodeContainer(this.myNodeController) 735 .borderWidth(1) 736 .width(300) 737 .height(100) 738 } 739 } 740 .padding({ left: 35, right: 35, top: 35, bottom: 35 }) 741 .width("100%") 742 .height("100%") 743 } 744} 745``` 746 747## Creating a FrameNode of a Specific Type Using typeNode 748 749By creating a FrameNode of a specific type using **typeNode**, you can obtain user-set attribute information through attribute obtaining APIs. 750 751```ts 752import { NodeController, FrameNode, UIContext, BuilderNode, typeNode } from '@kit.ArkUI'; 753import { BusinessError } from '@kit.BasicServicesKit'; 754 755class Params { 756 text: string = ""; 757 758 constructor(text: string) { 759 this.text = text; 760 } 761} 762 763@Builder 764function buildText(params: Params) { 765 Column() { 766 Text(params.text) 767 .id("buildText") 768 .border({width:1}) 769 .padding(1) 770 .fontSize(25) 771 .fontWeight(FontWeight.Bold) 772 .margin({ top: 10 }) 773 .visibility(Visibility.Visible) 774 .opacity(0.7) 775 .customProperty("key1", "value1") 776 .width(300) 777 } 778} 779 780const TEST_TAG : string = "FrameNode" 781class MyNodeController extends NodeController { 782 public frameNode: typeNode.Column | null = null; 783 public uiContext: UIContext | undefined = undefined; 784 private rootNode: FrameNode | null = null; 785 private textNode: BuilderNode<[Params]> | null = null; 786 public textTypeNode: typeNode.Text | null = null; 787 private message: string = "DEFAULT"; 788 789 makeNode(uiContext: UIContext): FrameNode | null { 790 this.rootNode = new FrameNode(uiContext); 791 this.uiContext = uiContext; 792 this.frameNode = typeNode.createNode(uiContext, "Column"); 793 this.frameNode.attribute 794 .width("100%") 795 .height("100%") 796 this.rootNode.appendChild(this.frameNode); 797 this.textNode = new BuilderNode(uiContext); 798 this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message)); 799 this.frameNode.appendChild(this.textNode.getFrameNode()); 800 this.textTypeNode = typeNode.createNode(uiContext, "Text"); 801 this.textTypeNode.initialize("textTypeNode") 802 .fontSize(25) 803 .visibility(Visibility.Visible) 804 .id("textTypeNode") 805 this.frameNode.appendChild(this.textTypeNode); 806 return this.rootNode; 807 } 808 removeChild(frameNode: FrameNode) 809 { 810 let parent = frameNode.getParent(); 811 if (parent) { 812 parent.removeChild(frameNode); 813 814 } 815 } 816 getUserConfigBorderWidth(frameNode: FrameNode) 817 { 818 let userConfigBorderWidth = frameNode?.getUserConfigBorderWidth(); // Obtain the border width set by the user. 819 console.log(TEST_TAG + JSON.stringify(userConfigBorderWidth)); 820 } 821 getUserConfigPadding(frameNode: FrameNode) 822 { 823 let userConfigPadding = frameNode?.getUserConfigPadding(); // Obtain the padding set by the user. 824 console.log(TEST_TAG + JSON.stringify(userConfigPadding)); 825 } 826 getUserConfigMargin(frameNode: FrameNode) 827 { 828 let userConfigMargin = frameNode?.getUserConfigMargin(); // Obtain the margin set by the user. 829 console.log(TEST_TAG + JSON.stringify(userConfigMargin)); 830 } 831 getUserConfigSize(frameNode: FrameNode) 832 { 833 let userConfigSize = frameNode?.getUserConfigSize(); // Obtain the width and height set by the user. 834 console.log(TEST_TAG + JSON.stringify(userConfigSize)); 835 } 836 getId(frameNode: FrameNode) 837 { 838 let id = frameNode?.getId(); // Obtain the node ID set by the user. 839 console.log(TEST_TAG + id); 840 } 841 getUniqueId(frameNode: FrameNode) 842 { 843 let uniqueId = frameNode?.getUniqueId(); // Obtain the unique node ID allocated by the system. 844 console.log(TEST_TAG + uniqueId); 845 } 846 getNodeType(frameNode: FrameNode) 847 { 848 let nodeType = frameNode?.getNodeType(); // Obtain the node type. 849 console.log(TEST_TAG + nodeType); 850 } 851 getOpacity(frameNode: FrameNode) 852 { 853 let opacity = frameNode?.getOpacity(); // Obtain the node opacity. 854 console.log(TEST_TAG + JSON.stringify(opacity)); 855 } 856 isVisible(frameNode: FrameNode) 857 { 858 let visible = frameNode?.isVisible(); // Obtain whether the node is visible. 859 console.log(TEST_TAG + JSON.stringify(visible)); 860 } 861 isClipToFrame(frameNode: FrameNode) 862 { 863 let clipToFrame = frameNode?.isClipToFrame(); // Obtain whether the node is clipped to the component area. 864 console.log(TEST_TAG + JSON.stringify(clipToFrame)); 865 } 866 isAttached(frameNode: FrameNode) 867 { 868 let attached = frameNode?.isAttached(); // Obtain whether a node is mounted to the main node tree. 869 console.log(TEST_TAG + JSON.stringify(attached)); 870 } 871 getInspectorInfo(frameNode: FrameNode) 872 { 873 let inspectorInfo = frameNode?.getInspectorInfo(); // Obtain the structure information of the node. 874 console.log(TEST_TAG + JSON.stringify(inspectorInfo)); 875 } 876} 877 878@Entry 879@Component 880struct Index { 881 private myNodeController: MyNodeController = new MyNodeController(); 882 @State index : number = 0; 883 build() { 884 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { 885 Column(){ 886 Text("This is a NodeContainer.") 887 .textAlign(TextAlign.Center).borderRadius(10).backgroundColor(0xFFFFFF) 888 .width('100%').fontSize(16) 889 NodeContainer(this.myNodeController) 890 .borderWidth(1) 891 .width(300) 892 .height(100) 893 } 894 Button("getUserConfigBorderWidth") 895 .width(300) 896 .onClick(()=>{ 897 const uiContext: UIContext = this.getUIContext(); 898 if (uiContext) { 899 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 900 if (node) { 901 this.myNodeController.getUserConfigBorderWidth(node); 902 } 903 } 904 }) 905 Button("getUserConfigPadding") 906 .width(300) 907 .onClick(()=>{ 908 const uiContext: UIContext = this.getUIContext(); 909 if (uiContext) { 910 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 911 if (node) { 912 this.myNodeController.getUserConfigPadding(node); 913 } 914 } 915 }) 916 Button("getUserConfigMargin") 917 .width(300) 918 .onClick(()=>{ 919 const uiContext: UIContext = this.getUIContext(); 920 if (uiContext) { 921 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 922 if (node) { 923 this.myNodeController.getUserConfigMargin(node); 924 } 925 } 926 }) 927 Button("getUserConfigSize") 928 .width(300) 929 .onClick(()=>{ 930 const uiContext: UIContext = this.getUIContext(); 931 if (uiContext) { 932 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 933 if (node) { 934 this.myNodeController.getUserConfigSize(node); 935 } 936 } 937 }) 938 Button("getId") 939 .width(300) 940 .onClick(()=>{ 941 const uiContext: UIContext = this.getUIContext(); 942 if (uiContext) { 943 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 944 if (node) { 945 this.myNodeController.getId(node); 946 } 947 } 948 }) 949 Button("getUniqueId") 950 .width(300) 951 .onClick(()=>{ 952 const uiContext: UIContext = this.getUIContext(); 953 if (uiContext) { 954 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 955 if (node) { 956 this.myNodeController.getUniqueId(node); 957 } 958 } 959 }) 960 Button("getNodeType") 961 .width(300) 962 .onClick(()=>{ 963 const uiContext: UIContext = this.getUIContext(); 964 if (uiContext) { 965 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 966 if (node) { 967 this.myNodeController.getNodeType(node); 968 } 969 } 970 }) 971 Button("getOpacity") 972 .width(300) 973 .onClick(()=>{ 974 const uiContext: UIContext = this.getUIContext(); 975 if (uiContext) { 976 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 977 if (node) { 978 this.myNodeController.getOpacity(node); 979 } 980 } 981 }) 982 Button("isVisible") 983 .width(300) 984 .onClick(()=>{ 985 const uiContext: UIContext = this.getUIContext(); 986 if (uiContext) { 987 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 988 if (node) { 989 this.myNodeController.isVisible(node); 990 } 991 } 992 }) 993 Button("isClipToFrame") 994 .width(300) 995 .onClick(()=>{ 996 const uiContext: UIContext = this.getUIContext(); 997 if (uiContext) { 998 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 999 if (node) { 1000 this.myNodeController.isClipToFrame(node); 1001 } 1002 } 1003 }) 1004 Button("isAttached") 1005 .width(300) 1006 .onClick(()=>{ 1007 const uiContext: UIContext = this.getUIContext(); 1008 if (uiContext) { 1009 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1010 if (node) { 1011 this.myNodeController.isAttached(node); 1012 } 1013 } 1014 }) 1015 Button("remove Text") 1016 .width(300) 1017 .onClick(()=>{ 1018 const uiContext: UIContext = this.getUIContext(); 1019 if (uiContext) { 1020 const node: FrameNode | null = uiContext.getFrameNodeById("textTypeNode") || null; 1021 if (node) { 1022 this.myNodeController.removeChild(node); 1023 this.myNodeController.isAttached(node); 1024 } 1025 } 1026 }) 1027 Button("getInspectorInfo") 1028 .width(300) 1029 .onClick(()=>{ 1030 const uiContext: UIContext = this.getUIContext(); 1031 if (uiContext) { 1032 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1033 if (node) { 1034 this.myNodeController.getInspectorInfo(node); 1035 } 1036 } 1037 }) 1038 Button("getCustomProperty") 1039 .width(300) 1040 .onClick(()=>{ 1041 const uiContext: UIContext = this.getUIContext(); 1042 if (uiContext) { 1043 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1044 if (node) { 1045 const property = node.getCustomProperty("key1"); 1046 console.log(TEST_TAG, JSON.stringify(property)); 1047 } 1048 } 1049 }) 1050 } 1051 .padding({ left: 35, right: 35, top: 35, bottom: 35 }) 1052 .width("100%") 1053 .height("100%") 1054 } 1055} 1056``` 1057 1058## Disassociating the Current FrameNode Object from the Entity FrameNode 1059 1060To disassociate the current **FrameNode** object from the entity FrameNode, call the [dispose](../reference/apis-arkui/js-apis-arkui-frameNode.md#dispose12) API. 1061 1062> **NOTE** 1063> 1064> 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**. 1065> 1066> 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. 1067 1068```ts 1069import { NodeController, FrameNode, BuilderNode } from '@kit.ArkUI'; 1070 1071@Component 1072struct TestComponent { 1073 build() { 1074 Column() { 1075 Text('This is a BuilderNode.') 1076 .fontSize(16) 1077 .fontWeight(FontWeight.Bold) 1078 } 1079 .width('100%') 1080 .backgroundColor(Color.Gray) 1081 } 1082 1083 aboutToAppear() { 1084 console.error('aboutToAppear'); 1085 } 1086 1087 aboutToDisappear() { 1088 console.error('aboutToDisappear'); 1089 } 1090} 1091 1092@Builder 1093function buildComponent() { 1094 TestComponent() 1095} 1096 1097class MyNodeController extends NodeController { 1098 private rootNode: FrameNode | null = null; 1099 private builderNode: BuilderNode<[]> | null = null; 1100 1101 makeNode(uiContext: UIContext): FrameNode | null { 1102 this.rootNode = new FrameNode(uiContext); 1103 this.builderNode = new BuilderNode(uiContext, { selfIdealSize: { width: 200, height: 100 } }); 1104 this.builderNode.build(new WrappedBuilder(buildComponent)); 1105 1106 const rootRenderNode = this.rootNode.getRenderNode(); 1107 if (rootRenderNode !== null) { 1108 rootRenderNode.size = { width: 200, height: 200 }; 1109 rootRenderNode.backgroundColor = 0xff00ff00; 1110 rootRenderNode.appendChild(this.builderNode!.getFrameNode()!.getRenderNode()); 1111 } 1112 1113 return this.rootNode; 1114 } 1115 1116 disposeFrameNode() { 1117 if (this.rootNode !== null && this.builderNode !== null) { 1118 this.rootNode.removeChild(this.builderNode.getFrameNode()); 1119 this.builderNode.dispose(); 1120 1121 this.rootNode.dispose(); 1122 } 1123 } 1124 1125 removeBuilderNode() { 1126 const rootRenderNode = this.rootNode!.getRenderNode(); 1127 if (rootRenderNode !== null && this.builderNode !== null && this.builderNode.getFrameNode() !== null) { 1128 rootRenderNode.removeChild(this.builderNode!.getFrameNode()!.getRenderNode()); 1129 } 1130 } 1131} 1132 1133@Entry 1134@Component 1135struct Index { 1136 private myNodeController: MyNodeController = new MyNodeController(); 1137 1138 build() { 1139 Column({ space: 4 }) { 1140 NodeContainer(this.myNodeController) 1141 Button('FrameNode dispose') 1142 .onClick(() => { 1143 this.myNodeController.disposeFrameNode(); 1144 }) 1145 .width('100%') 1146 } 1147 } 1148} 1149``` 1150 1151## Using the Lazy Loading Capability of FrameNode 1152 1153To 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. 1154 1155> **NOTE** 1156> 1157> Make sure the input parameter is not a negative number. If a negative value is provided, no action will be taken. 1158 1159```ts 1160import { FrameNode, NodeController, NodeAdapter, typeNode } from '@kit.ArkUI'; 1161 1162class MyNodeAdapter extends NodeAdapter { 1163 uiContext: UIContext 1164 cachePool: Array<FrameNode> = new Array(); 1165 changed: boolean = false 1166 reloadTimes: number = 0; 1167 data: Array<string> = new Array(); 1168 hostNode?: FrameNode 1169 1170 constructor(uiContext: UIContext, count: number) { 1171 super(); 1172 this.uiContext = uiContext; 1173 this.totalNodeCount = count; 1174 this.loadData(); 1175 } 1176 1177 reloadData(count: number): void { 1178 this.reloadTimes++; 1179 NodeAdapter.attachNodeAdapter(this, this.hostNode); 1180 this.totalNodeCount = count; 1181 this.loadData(); 1182 this.reloadAllItems(); 1183 } 1184 1185 refreshData(): void { 1186 let items = this.getAllAvailableItems() 1187 console.log("UINodeAdapter get All items:" + items.length); 1188 this.reloadAllItems(); 1189 } 1190 1191 detachData(): void { 1192 NodeAdapter.detachNodeAdapter(this.hostNode); 1193 this.reloadTimes = 0; 1194 } 1195 1196 loadData(): void { 1197 for (let i = 0; i < this.totalNodeCount; i++) { 1198 this.data[i] = "Adapter ListItem " + i + " r:" + this.reloadTimes; 1199 } 1200 } 1201 1202 changeData(from: number, count: number): void { 1203 this.changed = !this.changed; 1204 for (let i = 0; i < count; i++) { 1205 let index = i + from; 1206 this.data[index] = "Adapter ListItem " + (this.changed ? "changed:" : "") + index + " r:" + this.reloadTimes; 1207 } 1208 this.reloadItem(from, count); 1209 } 1210 1211 insertData(from: number, count: number): void { 1212 for (let i = 0; i < count; i++) { 1213 let index = i + from; 1214 this.data.splice(index, 0, "Adapter ListItem " + from + "-" + i); 1215 } 1216 this.insertItem(from, count); 1217 this.totalNodeCount += count; 1218 console.log("UINodeAdapter after insert count:" + this.totalNodeCount); 1219 } 1220 1221 removeData(from: number, count: number): void { 1222 let arr = this.data.splice(from, count); 1223 this.removeItem(from, count); 1224 this.totalNodeCount -= arr.length; 1225 console.log("UINodeAdapter after remove count:" + this.totalNodeCount); 1226 } 1227 1228 moveData(from: number, to: number): void { 1229 let tmp = this.data.splice(from, 1); 1230 this.data.splice(to, 0, tmp[0]); 1231 this.moveItem(from, to); 1232 } 1233 1234 onAttachToNode(target: FrameNode): void { 1235 console.log("UINodeAdapter onAttachToNode id:" + target.getUniqueId()); 1236 this.hostNode = target; 1237 } 1238 1239 onDetachFromNode(): void { 1240 console.log("UINodeAdapter onDetachFromNode"); 1241 } 1242 1243 onGetChildId(index: number): number { 1244 console.log("UINodeAdapter onGetChildId:" + index); 1245 return index; 1246 } 1247 1248 onCreateChild(index: number): FrameNode { 1249 console.log("UINodeAdapter onCreateChild:" + index); 1250 if (this.cachePool.length > 0) { 1251 let cacheNode = this.cachePool.pop(); 1252 if (cacheNode !== undefined) { 1253 console.log("UINodeAdapter onCreateChild reused id:" + cacheNode.getUniqueId()); 1254 let text = cacheNode?.getFirstChild(); 1255 let textNode = text as typeNode.Text; 1256 textNode?.initialize(this.data[index]).fontSize(20); 1257 return cacheNode; 1258 } 1259 } 1260 console.log("UINodeAdapter onCreateChild createNew"); 1261 let itemNode = typeNode.createNode(this.uiContext, "ListItem"); 1262 let textNode = typeNode.createNode(this.uiContext, "Text"); 1263 textNode.initialize(this.data[index]).fontSize(20); 1264 itemNode.appendChild(textNode); 1265 return itemNode; 1266 } 1267 1268 onDisposeChild(id: number, node: FrameNode): void { 1269 console.log("UINodeAdapter onDisposeChild:" + id); 1270 if (this.cachePool.length < 10) { 1271 if (!this.cachePool.includes(node)) { 1272 console.log("UINodeAdapter caching node id:" + node.getUniqueId()); 1273 this.cachePool.push(node); 1274 } 1275 } else { 1276 node.dispose(); 1277 } 1278 } 1279 1280 onUpdateChild(id: number, node: FrameNode): void { 1281 let index = id; 1282 let text = node.getFirstChild(); 1283 let textNode = text as typeNode.Text; 1284 textNode?.initialize(this.data[index]).fontSize(20); 1285 } 1286} 1287 1288class MyNodeAdapterController extends NodeController { 1289 rootNode: FrameNode | null = null; 1290 nodeAdapter: MyNodeAdapter | null = null; 1291 1292 makeNode(uiContext: UIContext): FrameNode | null { 1293 this.rootNode = new FrameNode(uiContext); 1294 let listNode = typeNode.createNode(uiContext, "List"); 1295 listNode.initialize({ space: 3 }).borderWidth(2).borderColor(Color.Black); 1296 this.rootNode.appendChild(listNode); 1297 this.nodeAdapter = new MyNodeAdapter(uiContext, 100); 1298 NodeAdapter.attachNodeAdapter(this.nodeAdapter, listNode); 1299 return this.rootNode; 1300 } 1301} 1302 1303@Entry 1304@Component 1305struct ListNodeTest { 1306 adapterController: MyNodeAdapterController = new MyNodeAdapterController(); 1307 1308 build() { 1309 Column() { 1310 Text("ListNode Adapter"); 1311 NodeContainer(this.adapterController) 1312 .width(300).height(300) 1313 .borderWidth(1).borderColor(Color.Black); 1314 Row() { 1315 Button("Reload") 1316 .onClick(() => { 1317 this.adapterController.nodeAdapter?.reloadData(50); 1318 }) 1319 Button("Change") 1320 .onClick(() => { 1321 this.adapterController.nodeAdapter?.changeData(5, 10) 1322 }) 1323 Button("Insert") 1324 .onClick(() => { 1325 this.adapterController.nodeAdapter?.insertData(10, 10); 1326 }) 1327 } 1328 1329 Row() { 1330 Button("Remove") 1331 .onClick(() => { 1332 this.adapterController.nodeAdapter?.removeData(10, 10); 1333 }) 1334 Button("Move") 1335 .onClick(() => { 1336 this.adapterController.nodeAdapter?.moveData(2, 5); 1337 }) 1338 Button("Refresh") 1339 .onClick(() => { 1340 this.adapterController.nodeAdapter?.refreshData(); 1341 }) 1342 Button("Detach") 1343 .onClick(() => { 1344 this.adapterController.nodeAdapter?.detachData(); 1345 }) 1346 } 1347 }.borderWidth(1) 1348 .width("100%") 1349 } 1350} 1351``` 1352