1# 自定义组件节点 (FrameNode) 2 3## 概述 4 5对于拥有自定义前端的第三方框架(如JSON、XML、DOM树等),需将特定的DSL转换为ArkUI的声明式描述。如下图描述了JSON定义的前端框架和ArkUI声明式描述的对应关系。 6 7 8 9上述转换过程需要依赖额外的数据驱动,绑定至[Builder](../quick-start/arkts-builder.md)中,较为复杂且性能欠佳。这类框架通常依赖于ArkUI的布局、事件处理、基础的节点操作和自定义能力。大部分组件通过自定义实现,但需结合使用部分系统组件以实现混合显示,如下图示例既使用了FrameNode的自定义方法进行绘制,又使用了系统组件Column及其子组件Text,通过BuilderNode的方式将其挂载到根节点的FrameNode上混合显示。 10 11 12 13[FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md)的设计初衷正是为了解决上述转换问题。FrameNode表示组件树中的实体节点,与自定义占位容器组件[NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md)相配合,实现在占位容器内构建一棵自定义的节点树。该节点树支持动态操作,如节点的增加、修改和删除。基础的FrameNode具备设置通用属性和事件回调的功能,同时提供完整的自定义能力,涵盖自定义测量、布局和绘制等方面。 14 15除此之外,ArkUI还提供了获取和遍历系统组件对应代理FrameNode对象的能力(下文简称代理节点)。代理节点能够用于遍历整个UI的树形结构,支持获取系统组件节点的详细信息,以及额外注册组件的事件监听回调。 16 17## 创建和删除节点 18 19FrameNode提供了节点创建和删除的能力。可以通过FrameNode的构造函数创建自定义FrameNode节点,通过构造函数创建的节点对应一个实体的节点。同时,可以通过FrameNode中的[dispose](../reference/apis-arkui/js-apis-arkui-frameNode.md#dispose12)接口来实现与实体节点的绑定关系的解除。 20 21> **说明:** 22> 23> - 在创建FrameNode对象的时候需要传入必选参数UIContext,若未传入UIContext对象或者传入不合法,则节点创建抛出异常。 24> 25> - 自定义占位组件将节点进行显示的时候需要保证UI上下文一致,否则会出现显示异常。 26> 27> - 若不持有FrameNode对象,则该对象会在GC的时候被回收。 28 29## 判断节点是否可修改 30 31[isModifiable](../reference/apis-arkui/js-apis-arkui-frameNode.md#ismodifiable12)用于查询当前节点类型是否为系统组件的代理节点。当FrameNode节点作为系统组件的代理节点的时候,该节点不可修改。即无法修改代理节点的自身属性以及其子节点的结构。 32 33## 获取对应的RenderNode节点 34 35FrameNode提供了[getRenderNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#getrendernode)接口,用于获取FrameNode中的RenderNode。可以通过对获取到的RenderNode对象进行操作,动态修改FrameNode上绘制相关的属性,具体可修改的属性参考[RenderNode](arkts-user-defined-arktsNode-renderNode.md)的接口。 36 37> **说明:** 38> 39> - 无法获取系统组件代理FrameNode的RenderNode对象。 40> 41> - BuilderNode中调用[getFrameNode](../reference/apis-arkui/js-apis-arkui-builderNode.md#getframenode)获取得到的FrameNode节点对象中,可以通过getRenderNode获取对应的根节点的RenderNode对象。 42 43## 操作节点树 44 45FrameNode提供了节点的增、删、查、改的能力,能够修改非代理节点的子树结构。可以对所有FrameNode的节点的父子节点做出查询操作,并返回查询结果。 46 47> **说明:** 48> 49> 对节点进行增、删、改操作的时候,会对非法操作抛出异常信息。 50> 51> 通过查询获得的原生组件的代理节点,仅具备查询节点信息的作用,不具备修改节点属性的功能。代理节点不持有组件的实体节点,即不影响对应的节点的生命周期。 52> 53> 查询节点仅查询获得UI相关的节点,不返回语法节点。 54> 55> 使用自定义组件的场景下,可能查询获得自定义组件的新增节点,节点类型为“\_\_Common\_\_”。 56 57```ts 58import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI'; 59import { BusinessError } from '@kit.BasicServicesKit'; 60 61const TEST_TAG: string = "FrameNode" 62 63class Params { 64 text: string = "this is a text" 65} 66 67@Builder 68function buttonBuilder(params: Params) { 69 Column({ space: 10 }) { 70 Button(params.text) 71 .fontSize(12) 72 .borderRadius(8) 73 .borderWidth(2) 74 .backgroundColor(Color.Orange) 75 76 Button(params.text) 77 .fontSize(12) 78 .borderRadius(8) 79 .borderWidth(2) 80 .backgroundColor(Color.Pink) 81 } 82} 83 84class MyNodeController extends NodeController { 85 public buttonNode: BuilderNode<[Params]> | null = null; 86 public frameNode: FrameNode | null = null; 87 public childList: Array<FrameNode> = new Array<FrameNode>(); 88 public rootNode: FrameNode | null = null; 89 private uiContext: UIContext | null = null; 90 private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(buttonBuilder); 91 92 makeNode(uiContext: UIContext): FrameNode | null { 93 this.uiContext = uiContext; 94 if (this.rootNode == null) { 95 this.rootNode = new FrameNode(uiContext); 96 this.rootNode.commonAttribute 97 .width("50%") 98 .height(100) 99 .borderWidth(1) 100 .backgroundColor(Color.Gray) 101 } 102 103 if (this.frameNode == null) { 104 this.frameNode = new FrameNode(uiContext); 105 this.frameNode.commonAttribute 106 .width("100%") 107 .height(50) 108 .borderWidth(1) 109 .position({ x: 200, y: 0 }) 110 .backgroundColor(Color.Pink); 111 this.rootNode.appendChild(this.frameNode); 112 } 113 if (this.buttonNode == null) { 114 this.buttonNode = new BuilderNode<[Params]>(uiContext); 115 this.buttonNode.build(this.wrapBuilder, { text: "This is a Button" }) 116 this.rootNode.appendChild(this.buttonNode.getFrameNode()) 117 } 118 return this.rootNode; 119 } 120 121 operationFrameNodeWithFrameNode(frameNode: FrameNode | undefined | null) { 122 if (frameNode) { 123 console.log(TEST_TAG + " get ArkTSNode success.") 124 console.log(TEST_TAG + " check rootNode whether is modifiable " + frameNode.isModifiable()); 125 } 126 if (this.uiContext) { 127 let frameNode1 = new FrameNode(this.uiContext); 128 let frameNode2 = new FrameNode(this.uiContext); 129 frameNode1.commonAttribute.size({ width: 50, height: 50 }) 130 .backgroundColor(Color.Black) 131 .position({ x: 50, y: 60 }) 132 frameNode2.commonAttribute.size({ width: 50, height: 50 }) 133 .backgroundColor(Color.Orange) 134 .position({ x: 120, y: 60 }) 135 try { 136 frameNode?.appendChild(frameNode1); 137 console.log(TEST_TAG + " appendChild success "); 138 } catch (err) { 139 console.log(TEST_TAG + " appendChild fail :" + (err as BusinessError).code + " : " + (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("验证FrameNode子节点的增、删、改功能") 205 Button("对自定义FrameNode进行操作") 206 .fontSize(16) 207 .width(400) 208 .onClick(() => { 209 // 对FrameNode节点进行增、删、改操作,正常实现。 210 this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.frameNode); 211 }) 212 Button("对BuilderNode中的代理节点进行操作") 213 .fontSize(16) 214 .width(400) 215 .onClick(() => { 216 // 对BuilderNode代理节点进行增、删、改操作,捕获异常信息。 217 this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.buttonNode?.getFrameNode()); 218 }) 219 Button("对原生组件中的代理节点进行操作") 220 .fontSize(16) 221 .width(400) 222 .onClick(() => { 223 // 对代理节点进行增、删、改操作,捕获异常信息。 224 this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.rootNode?.getParent()); 225 }) 226 } 227 } 228 229 ListItem() { 230 Column({ space: 5 }) { 231 Text("验证FrameNode添加子节点的特殊场景") 232 Button("新增BuilderNode的代理节点") 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("新增原生组件代理节点") 241 .fontSize(16) 242 .width(400) 243 .onClick(() => { 244 this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, this.myNodeController?.rootNode?.getParent()); 245 }) 246 Button("新增已有父节点的自定义节点") 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("验证FrameNode节点的查询功能") 258 Button("对自定义FrameNode进行操作") 259 .fontSize(16) 260 .width(400) 261 .onClick(() => { 262 // 对FrameNode节点进行进行查询。当前节点为NodeContainer的子节点。 263 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode); 264 setTimeout(() => { 265 // 对FrameNode节点进行进行查询。rootNode下的第一个子节点。 266 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.frameNode); 267 }, 2000) 268 }) 269 Button("对BuilderNode中的代理节点进行操作") 270 .fontSize(16) 271 .width(400) 272 .onClick(() => { 273 // 对BuilderNode代理节点进行进行查询。当前节点为BuilderNode中的Column节点。 274 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.buttonNode?.getFrameNode()); 275 }) 276 Button("对原生组件中的代理节点进行操作") 277 .fontSize(16) 278 .width(400) 279 .onClick(() => { 280 // 对代理节点进行查询。当前节点为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## 设置节点通用属性和事件回调 314 315FrameNode提供了[commonAttribute](../reference/apis-arkui/js-apis-arkui-frameNode.md#commonattribute12)和[commonEvent](../reference/apis-arkui/js-apis-arkui-frameNode.md#commonevent12)两个对象用于对设置节点的[通用属性](../reference/apis-arkui/arkui-ts/ts-universal-attributes-size.md)和[设置事件回调](../reference/apis-arkui/arkui-ts/ts-uicommonevent.md)。 316 317> **说明:** 318> 319> - 由于代理节点的属性不可修改,因此通过代理节点的commonAttribute修改节点的基础属性不生效。 320> 321> - 设置的基础事件与原生组件定义的事件平行,参与事件竞争。设置的基础事件不覆盖原生组件事件。同时设置两个事件回调的时候,优先回调原生组件事件。 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 // 对rootNode进行属性修改,该节点为自定义的FrameNode节点,修改生效 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 // 对frameNode进行属性修改,该节点为自定义的FrameNode节点,修改生效 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 // 对BuilderNode中获取的FrameNode进行属性修改,该节点非自定义的FrameNode节点,修改不生效 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("修改节点通用属性-宽高") 403 Button("modify ArkTS-FrameNode") 404 .onClick(() => { 405 // 获取到的是当前页面中的开发者创建的FrameNode对象,该节点可修改。即节点大小与位置。 406 console.log("Check the weather 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 // 获取到的是当前页面中的BuilderNode的根节点,该节点不可修改。即节点大小与位置未发生改变。 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 // rootNode调用getParent()获取到的是当前页面中的NodeContainer节点,该节点不可修改。即节点大小与位置未发生改变。 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("修改节点点击事件") 440 Button("add click event to ArkTS-FrameNode") 441 .onClick(() => { 442 // 获取到的是当前页面中的开发者创建的FrameNode对象,该节点可增加点击事件。 443 // 增加的点击事件参与事件竞争,即点击事件会在该节点被消费且不不再向父组件冒泡。 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 // 获取到的是当前页面中的BuilderNode的根节点,该类节点可增加点击事件。 451 // 点击的时候优先回调通过原生组件接口设置的click事件回调,然后回调通过commonEvent增加的click监听。 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 // rootNode调用getParent()获取到的是当前页面中的NodeContainer节点,该类节点可增加点击事件。 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## 自定义测量布局与绘制 481 482通过重写[onDraw](../reference/apis-arkui/js-apis-arkui-frameNode.md#ondraw12)方法,可以自定义FrameNode的绘制内容。[invalidate](../reference/apis-arkui/js-apis-arkui-frameNode.md#invalidate12)接口可以主动触发节点的重新绘制。 483 484通过重写[onMeasure](../reference/apis-arkui/js-apis-arkui-frameNode.md#onmeasure12)可以自定义FrameNode的测量方式,使用[measure](../reference/apis-arkui/js-apis-arkui-frameNode.md#measure12)可以主动传递布局约束触发重新测量。 485 486通过重写[onLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#onlayout12)方法可以自定义FrameNode的布局方式,使用[layout](../reference/apis-arkui/js-apis-arkui-frameNode.md#layout12)方法可以主动传递位置信息并触发重新布局。 487 488[setNeedsLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#setneedslayout12)可以将当前节点标记,在下一帧触发重新布局。 489 490> **说明:** 491> 492> - 对节点进行dispose解引用后,由于FrameNode对象不再对应一个实体节点,invalidate无法触发原有绑定节点的刷新。 493> 494> - 通过onDraw方法进行的自定义绘制,绘制内容大小无法超出组件大小。 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## 查找节点及获取基础信息 622 623FrameNode提供了查询接口用于返回实体节点的基础信息。具体返回的信息内容参考FrameNode中提供的接口。 624 625查找获得FrameNode的方式包括三种: 626 6271. 使用[getFrameNodeById](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyid12)获取。 628 6292. 使用[getFrameNodeByUniqueId](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyuniqueid12)获取。 630 6313. 通过[无感监听](../reference/apis-arkui/js-apis-arkui-observer.md)获取。 632 633> **说明:** 634> 635> 1、当前接口提供的可查询的信息包括: 636> 637> - 节点大小:[getMeasuredSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getmeasuredsize12),[getUserConfigSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigsize12) 638> 639> - 布局信息:[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> - 节点信息:[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类型节点,例如:JsView节点、[Span](../../application-dev/reference/apis-arkui/arkui-ts/ts-basic-components-span.md)、[ContainerSpan](../../application-dev/reference/apis-arkui/arkui-ts/ts-basic-components-containerspan.md)、[ContentSlot](../../application-dev/reference/apis-arkui/arkui-ts/ts-components-contentSlot.md)、[ForEach](../../application-dev/reference/apis-arkui/arkui-ts/ts-rendering-control-foreach.md)、[LazyForEach](../../application-dev/reference/apis-arkui/arkui-ts/ts-rendering-control-lazyforeach.md)、if/else组件等。 644 645## 获取节点位置偏移信息 646 647FrameNode提供了查询节点相对窗口、父组件以及屏幕位置偏移的信息接口([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(); // 获取FrameNode相对于窗口的位置偏移 665 console.log(TEST_TAG + JSON.stringify(positionToWindow)); 666 } 667 getPositionToParent() 668 { 669 let positionToParent = this.rootNode?.getPositionToParent(); // 获取FrameNode相对于父组件的位置偏移 670 console.log(TEST_TAG + JSON.stringify(positionToParent)); 671 } 672 getPositionToScreen() 673 { 674 let positionToScreen = this.rootNode?.getPositionToScreen(); // 获取FrameNode相对于屏幕的位置偏移 675 console.log(TEST_TAG + JSON.stringify(positionToScreen)); 676 } 677 getPositionToWindowWithTransform() 678 { 679 let positionToWindowWithTransform = this.rootNode?.getPositionToWindowWithTransform(); // 获取FrameNode相对于窗口带有绘制属性的位置偏移 680 console.log(TEST_TAG + JSON.stringify(positionToWindowWithTransform)); 681 } 682 getPositionToParentWithTransform() 683 { 684 let positionToParentWithTransform = this.rootNode?.getPositionToParentWithTransform(); // 获取FrameNode相对于父组件带有绘制属性的位置偏移 685 console.log(TEST_TAG + JSON.stringify(positionToParentWithTransform)); 686 } 687 getPositionToScreenWithTransform() 688 { 689 let positionToScreenWithTransform = this.rootNode?.getPositionToScreenWithTransform(); // 获取FrameNode相对于屏幕带有绘制属性的位置偏移 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## 通过typeNode创建具体类型的FrameNode节点 748 749通过TypeNode创建具体类型的FrameNode节点,可以根据属性获取接口来检索用户设置的属性信息。 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(); // 获取用户设置的边框宽度 819 console.log(TEST_TAG + JSON.stringify(userConfigBorderWidth)); 820 } 821 getUserConfigPadding(frameNode: FrameNode) 822 { 823 let userConfigPadding = frameNode?.getUserConfigPadding(); // 获取用户设置的内边距 824 console.log(TEST_TAG + JSON.stringify(userConfigPadding)); 825 } 826 getUserConfigMargin(frameNode: FrameNode) 827 { 828 let userConfigMargin = frameNode?.getUserConfigMargin(); // 获取用户设置的外边距 829 console.log(TEST_TAG + JSON.stringify(userConfigMargin)); 830 } 831 getUserConfigSize(frameNode: FrameNode) 832 { 833 let userConfigSize = frameNode?.getUserConfigSize(); // 获取用户设置的宽高 834 console.log(TEST_TAG + JSON.stringify(userConfigSize)); 835 } 836 getId(frameNode: FrameNode) 837 { 838 let id = frameNode?.getId(); // 获取用户设置的节点ID 839 console.log(TEST_TAG + id); 840 } 841 getUniqueId(frameNode: FrameNode) 842 { 843 let uniqueId = frameNode?.getUniqueId(); // 获取系统分配的唯一标识的节点UniqueID 844 console.log(TEST_TAG + uniqueId); 845 } 846 getNodeType(frameNode: FrameNode) 847 { 848 let nodeType = frameNode?.getNodeType(); // 获取节点的类型 849 console.log(TEST_TAG + nodeType); 850 } 851 getOpacity(frameNode: FrameNode) 852 { 853 let opacity = frameNode?.getOpacity(); // 获取节点的不透明度 854 console.log(TEST_TAG + JSON.stringify(opacity)); 855 } 856 isVisible(frameNode: FrameNode) 857 { 858 let visible = frameNode?.isVisible(); // 获取节点是否可见 859 console.log(TEST_TAG + JSON.stringify(visible)); 860 } 861 isClipToFrame(frameNode: FrameNode) 862 { 863 let clipToFrame = frameNode?.isClipToFrame(); // 获取节点是否是剪裁到组件区域 864 console.log(TEST_TAG + JSON.stringify(clipToFrame)); 865 } 866 isAttached(frameNode: FrameNode) 867 { 868 let attached = frameNode?.isAttached(); // 获取节点是否被挂载到主节点树上 869 console.log(TEST_TAG + JSON.stringify(attached)); 870 } 871 getInspectorInfo(frameNode: FrameNode) 872 { 873 let inspectorInfo = frameNode?.getInspectorInfo(); // 获取节点的结构信息 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## 解除当前FrameNode对象对实体FrameNode节点的引用关系 1059 1060使用[dispose](../reference/apis-arkui/js-apis-arkui-frameNode.md#dispose12)接口可以立即解除当前FrameNode对象对实体FrameNode节点的引用关系。 1061 1062> **说明:** 1063> 1064> 在调用dispose方法后,FrameNode对象不再对应任何实际的FrameNode节点。此时,若尝试调用以下查询接口:getMeasuredSize、getLayoutPosition、getUserConfigBorderWidth、getUserConfigPadding、getUserConfigMargin、getUserConfigSize,将导致应用程序触发jscrash。 1065> 1066> 通过[getUniqueId](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuniqueid12)可以判断当前FrameNode是否对应一个实体FrameNode节点。当UniqueId大于0时表示该对象对应一个实体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## FrameNode的数据懒加载能力 1152 1153提供[NodeAdapter](../reference/apis-arkui/js-apis-arkui-frameNode.md#nodeadapter12)对象替代ArkTS侧的LazyForEach功能,提供自定义节点的数据懒加载功能,实现按需迭代数据。 1154 1155> **说明:** 1156> 1157> 入参不能为负数,入参为负数时不做处理。 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