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 + " : " + 140 (err as BusinessError).message); 141 } 142 try { 143 frameNode?.insertChildAfter(frameNode2, null); 144 console.log(TEST_TAG + " insertChildAfter success "); 145 } catch (err) { 146 console.log(TEST_TAG + " insertChildAfter fail : " + (err as BusinessError).code + " : " + 147 (err as BusinessError).message); 148 } 149 setTimeout(() => { 150 try { 151 frameNode?.removeChild(frameNode?.getChild(0)) 152 console.log(TEST_TAG + " removeChild success "); 153 } catch (err) { 154 console.log(TEST_TAG + " removeChild fail : " + (err as BusinessError).code + " : " + 155 (err as BusinessError).message); 156 } 157 }, 2000) 158 setTimeout(() => { 159 try { 160 frameNode?.clearChildren(); 161 console.log(TEST_TAG + " clearChildren success "); 162 } catch (err) { 163 console.log(TEST_TAG + " clearChildren fail : " + (err as BusinessError).code + " : " + 164 (err as BusinessError).message); 165 } 166 }, 4000) 167 } 168 } 169 170 testInterfaceAboutSearch(frameNode: FrameNode | undefined | null): string { 171 let result: string = ""; 172 if (frameNode) { 173 result = result + `current node is ${frameNode.getNodeType()} \n`; 174 result = result + `parent node is ${frameNode.getParent()?.getNodeType()} \n`; 175 result = result + `child count is ${frameNode.getChildrenCount()} \n`; 176 result = result + `first child node is ${frameNode.getFirstChild()?.getNodeType()} \n`; 177 result = result + `second child node is ${frameNode.getChild(1)?.getNodeType()} \n`; 178 result = result + `previousSibling node is ${frameNode.getPreviousSibling()?.getNodeType()} \n`; 179 result = result + `nextSibling node is ${frameNode.getNextSibling()?.getNodeType()} \n`; 180 } 181 return result; 182 } 183 184 checkAppendChild(parent: FrameNode | undefined | null, child: FrameNode | undefined | null) { 185 try { 186 if (parent && child) { 187 parent.appendChild(child); 188 console.log(TEST_TAG + " appendChild success "); 189 } 190 } catch (err) { 191 console.log(TEST_TAG + " appendChild fail : " + (err as BusinessError).code + " : " + 192 (err as BusinessError).message); 193 } 194 } 195} 196 197@Entry 198@Component 199struct Index { 200 @State index: number = 0; 201 @State result: string = "" 202 private myNodeController: MyNodeController = new MyNodeController(); 203 204 build() { 205 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { 206 List({ space: 20, initialIndex: 0 }) { 207 ListItem() { 208 Column({ space: 5 }) { 209 Text("验证FrameNode子节点的增、删、改功能") 210 Button("对自定义FrameNode进行操作") 211 .fontSize(16) 212 .width(400) 213 .onClick(() => { 214 // 对FrameNode节点进行增、删、改操作,正常实现。 215 this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.frameNode); 216 }) 217 Button("对BuilderNode中的代理节点进行操作") 218 .fontSize(16) 219 .width(400) 220 .onClick(() => { 221 // 对BuilderNode代理节点进行增、删、改操作,捕获异常信息。 222 this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.buttonNode?.getFrameNode()); 223 }) 224 Button("对原生组件中的代理节点进行操作") 225 .fontSize(16) 226 .width(400) 227 .onClick(() => { 228 // 对代理节点进行增、删、改操作,捕获异常信息。 229 this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.rootNode?.getParent()); 230 }) 231 } 232 } 233 234 ListItem() { 235 Column({ space: 5 }) { 236 Text("验证FrameNode添加子节点的特殊场景") 237 Button("新增BuilderNode的代理节点") 238 .fontSize(16) 239 .width(400) 240 .onClick(() => { 241 let buttonNode = new BuilderNode<[Params]>(this.getUIContext()); 242 buttonNode.build(wrapBuilder<[Params]>(buttonBuilder), { text: "BUTTON" }) 243 this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, buttonNode?.getFrameNode()); 244 }) 245 Button("新增原生组件代理节点") 246 .fontSize(16) 247 .width(400) 248 .onClick(() => { 249 this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, 250 this.myNodeController?.rootNode?.getParent()); 251 }) 252 Button("新增已有父节点的自定义节点") 253 .fontSize(16) 254 .width(400) 255 .onClick(() => { 256 this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, 257 this.myNodeController?.rootNode); 258 }) 259 } 260 } 261 262 ListItem() { 263 Column({ space: 5 }) { 264 Text("验证FrameNode节点的查询功能") 265 Button("对自定义FrameNode进行操作") 266 .fontSize(16) 267 .width(400) 268 .onClick(() => { 269 // 对FrameNode节点进行进行查询。当前节点为NodeContainer的子节点。 270 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode); 271 setTimeout(() => { 272 // 对FrameNode节点进行进行查询。rootNode下的第一个子节点。 273 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.frameNode); 274 }, 2000) 275 }) 276 Button("对BuilderNode中的代理节点进行操作") 277 .fontSize(16) 278 .width(400) 279 .onClick(() => { 280 // 对BuilderNode代理节点进行进行查询。当前节点为BuilderNode中的Column节点。 281 this.result = 282 this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.buttonNode?.getFrameNode()); 283 }) 284 Button("对原生组件中的代理节点进行操作") 285 .fontSize(16) 286 .width(400) 287 .onClick(() => { 288 // 对代理节点进行查询。当前节点为NodeContainer。 289 this.result = 290 this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode?.getParent()); 291 }) 292 } 293 } 294 }.height("50%") 295 296 Text(`Result:\n${this.result}`) 297 .fontSize(16) 298 .width(400) 299 .height(200) 300 .padding(30) 301 .borderWidth(1) 302 Column() { 303 Text("This is a NodeContainer.") 304 .textAlign(TextAlign.Center) 305 .borderRadius(10) 306 .backgroundColor(0xFFFFFF) 307 .width('100%') 308 .fontSize(16) 309 NodeContainer(this.myNodeController) 310 .borderWidth(1) 311 .width(400) 312 .height(150) 313 } 314 } 315 .padding({ 316 left: 35, 317 right: 35, 318 top: 35, 319 bottom: 35 320 }) 321 .width("100%") 322 .height("100%") 323 } 324} 325``` 326 327## 设置节点通用属性和事件回调 328 329FrameNode提供了[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-component-general-attributes.md)和[设置事件回调](../reference/apis-arkui/arkui-ts/ts-uicommonevent.md)。 330 331> **说明:** 332> 333> - 由于代理节点的属性不可修改,因此通过代理节点的commonAttribute修改节点的基础属性不生效。 334> 335> - 设置的基础事件与原生组件定义的事件平行,参与事件竞争。设置的基础事件不覆盖原生组件事件。同时设置两个事件回调的时候,优先回调原生组件事件。 336 337```ts 338import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI' 339 340class Params { 341 text: string = "this is a text" 342} 343 344@Builder 345function buttonBuilder(params: Params) { 346 Button(params.text) 347 .fontSize(12) 348 .borderRadius(8) 349 .borderWidth(2) 350 .backgroundColor(Color.Orange) 351 .onClick((event: ClickEvent) => { 352 console.log(`Button ${JSON.stringify(event)}`); 353 }) 354} 355 356class MyNodeController extends NodeController { 357 public buttonNode: BuilderNode<[Params]> | null = null; 358 public frameNode: FrameNode | null = null; 359 public rootNode: FrameNode | null = null; 360 private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(buttonBuilder); 361 362 makeNode(uiContext: UIContext): FrameNode | null { 363 if (this.rootNode == null) { 364 this.rootNode = new FrameNode(uiContext); 365 // 对rootNode进行属性修改,该节点为自定义的FrameNode节点,修改生效 366 this.rootNode.commonAttribute 367 .width("100%") 368 .height(100) 369 .borderWidth(1) 370 .backgroundColor(Color.Gray) 371 } 372 373 if (this.frameNode == null) { 374 this.frameNode = new FrameNode(uiContext); 375 // 对frameNode进行属性修改,该节点为自定义的FrameNode节点,修改生效 376 this.frameNode.commonAttribute 377 .width("50%") 378 .height(50) 379 .borderWidth(1) 380 .backgroundColor(Color.Pink); 381 this.rootNode.appendChild(this.frameNode); 382 } 383 if (this.buttonNode == null) { 384 this.buttonNode = new BuilderNode<[Params]>(uiContext); 385 this.buttonNode.build(this.wrapBuilder, { text: "This is a Button" }) 386 // 对BuilderNode中获取的FrameNode进行属性修改,该节点非自定义的FrameNode节点,修改不生效 387 this.buttonNode?.getFrameNode()?.commonAttribute.position({ x: 100, y: 100 }) 388 this.rootNode.appendChild(this.buttonNode.getFrameNode()) 389 } 390 return this.rootNode; 391 } 392 393 modifyNode(frameNode: FrameNode | null | undefined, sizeValue: SizeOptions, positionValue: Position) { 394 if (frameNode) { 395 frameNode.commonAttribute.size(sizeValue).position(positionValue); 396 } 397 } 398 399 addClickEvent(frameNode: FrameNode | null | undefined) { 400 if (frameNode) { 401 frameNode.commonEvent.setOnClick((event: ClickEvent) => { 402 console.log(`FrameNode ${JSON.stringify(event)}`); 403 }) 404 } 405 } 406} 407 408@Entry 409@Component 410struct Index { 411 private myNodeController: MyNodeController = new MyNodeController(); 412 413 build() { 414 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { 415 Column({ space: 10 }) { 416 Text("修改节点通用属性-宽高") 417 Button("modify ArkTS-FrameNode") 418 .onClick(() => { 419 // 获取到的是当前页面中的开发者创建的FrameNode对象,该节点可修改。即节点大小与位置。 420 console.log("Check the weather the node can be modified " + this.myNodeController?.frameNode 421 ?.isModifiable()); 422 this.myNodeController.modifyNode(this.myNodeController?.frameNode, { width: 150, height: 100 }, { 423 x: 100, 424 y: 0 425 }) 426 }) 427 Button("modify FrameNode get by BuilderNode") 428 .onClick(() => { 429 // 获取到的是当前页面中的BuilderNode的根节点,该节点不可修改。即节点大小与位置未发生改变。 430 console.log("Check the weather the node can be modified " + 431 this.myNodeController?.buttonNode?.getFrameNode() 432 ?.isModifiable()); 433 this.myNodeController.modifyNode(this.myNodeController?.buttonNode?.getFrameNode(), { 434 width: 100, 435 height: 100 436 }, { x: 50, y: 50 }) 437 }) 438 Button("modify proxyFrameNode get by search") 439 .onClick(() => { 440 // rootNode调用getParent()获取到的是当前页面中的NodeContainer节点,该节点不可修改。即节点大小与位置未发生改变。 441 console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent() 442 ?.isModifiable()); 443 this.myNodeController.modifyNode(this.myNodeController?.rootNode?.getParent(), { 444 width: 500, 445 height: 500 446 }, { 447 x: 0, 448 y: 0 449 }) 450 }) 451 } 452 .padding({ 453 left: 35, 454 right: 35, 455 top: 35, 456 bottom: 35 457 }) 458 459 Column({ space: 10 }) { 460 Text("修改节点点击事件") 461 Button("add click event to ArkTS-FrameNode") 462 .onClick(() => { 463 // 获取到的是当前页面中的开发者创建的FrameNode对象,该节点可增加点击事件。 464 // 增加的点击事件参与事件竞争,即点击事件会在该节点被消费且不不再向父组件冒泡。 465 console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent() 466 ?.isModifiable()); 467 this.myNodeController.addClickEvent(this.myNodeController?.frameNode) 468 }) 469 Button("add click event to FrameNode get by BuilderNode") 470 .onClick(() => { 471 // 获取到的是当前页面中的BuilderNode的根节点,该类节点可增加点击事件。 472 // 点击的时候优先回调通过原生组件接口设置的click事件回调,然后回调通过commonEvent增加的click监听。 473 console.log("Check the weather the node can be modified " + 474 this.myNodeController?.buttonNode?.getFrameNode() 475 ?.isModifiable()); 476 this.myNodeController.addClickEvent(this.myNodeController?.buttonNode?.getFrameNode()) 477 }) 478 Button("add click event to proxyFrameNode get by search") 479 .onClick(() => { 480 // rootNode调用getParent()获取到的是当前页面中的NodeContainer节点,该类节点可增加点击事件。 481 console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent() 482 ?.isModifiable()); 483 this.myNodeController.addClickEvent(this.myNodeController?.rootNode?.getParent()); 484 }) 485 } 486 .padding({ 487 left: 35, 488 right: 35, 489 top: 35, 490 bottom: 35 491 }) 492 493 NodeContainer(this.myNodeController) 494 .borderWidth(1) 495 .width("100%") 496 .height(100) 497 .onClick((event: ClickEvent) => { 498 console.log(`NodeContainer ${JSON.stringify(event)}`); 499 }) 500 } 501 .padding({ 502 left: 35, 503 right: 35, 504 top: 35, 505 bottom: 35 506 }) 507 .width("100%") 508 .height("100%") 509 } 510} 511``` 512 513## 自定义测量布局与绘制 514 515通过重写[onDraw](../reference/apis-arkui/js-apis-arkui-frameNode.md#ondraw12)方法,可以自定义FrameNode的绘制内容。[invalidate](../reference/apis-arkui/js-apis-arkui-frameNode.md#invalidate12)接口可以主动触发节点的重新绘制。 516 517通过重写[onMeasure](../reference/apis-arkui/js-apis-arkui-frameNode.md#onmeasure12)可以自定义FrameNode的测量方式,使用[measure](../reference/apis-arkui/js-apis-arkui-frameNode.md#measure12)可以主动传递布局约束触发重新测量。 518 519通过重写[onLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#onlayout12)方法可以自定义FrameNode的布局方式,使用[layout](../reference/apis-arkui/js-apis-arkui-frameNode.md#layout12)方法可以主动传递位置信息并触发重新布局。 520 521[setNeedsLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#setneedslayout12)可以将当前节点标记,在下一帧触发重新布局。 522 523> **说明:** 524> 525> - 对节点进行dispose解引用后,由于FrameNode对象不再对应一个实体节点,invalidate无法触发原有绑定节点的刷新。 526> 527> - 通过onDraw方法进行的自定义绘制,绘制内容大小无法超出组件大小。 528 529```ts 530import { DrawContext, FrameNode, NodeController, Position, Size, UIContext, LayoutConstraint } from '@kit.ArkUI'; 531import { drawing } from '@kit.ArkGraphics2D'; 532 533function GetChildLayoutConstraint(constraint: LayoutConstraint, child: FrameNode): LayoutConstraint { 534 const size = child.getUserConfigSize(); 535 const width = Math.max( 536 Math.min(constraint.maxSize.width, size.width.value), 537 constraint.minSize.width 538 ); 539 const height = Math.max( 540 Math.min(constraint.maxSize.height, size.height.value), 541 constraint.minSize.height 542 ); 543 const finalSize: Size = { width, height }; 544 const res: LayoutConstraint = { 545 maxSize: finalSize, 546 minSize: finalSize, 547 percentReference: finalSize 548 }; 549 550 return res; 551} 552 553class MyFrameNode extends FrameNode { 554 public width: number = 100; 555 public offsetY: number = 0; 556 private space: number = 1; 557 558 onMeasure(constraint: LayoutConstraint): void { 559 let sizeRes: Size = { width: vp2px(100), height: vp2px(100) }; 560 for (let i = 0; i < this.getChildrenCount(); i++) { 561 let child = this.getChild(i); 562 if (child) { 563 let childConstraint = GetChildLayoutConstraint(constraint, child); 564 child.measure(childConstraint); 565 let size = child.getMeasuredSize(); 566 sizeRes.height += size.height + this.space; 567 sizeRes.width = Math.max(sizeRes.width, size.width); 568 } 569 } 570 this.setMeasuredSize(sizeRes); 571 } 572 573 onLayout(position: Position): void { 574 for (let i = 0; i < this.getChildrenCount(); i++) { 575 let child = this.getChild(i); 576 if (child) { 577 child.layout({ 578 x: vp2px(100), 579 y: vp2px(this.offsetY) 580 }); 581 let layoutPosition = child.getLayoutPosition(); 582 console.log("child position:" + JSON.stringify(layoutPosition)); 583 } 584 } 585 this.setLayoutPosition(position); 586 } 587 588 onDraw(context: DrawContext) { 589 const canvas = context.canvas; 590 const pen = new drawing.Pen(); 591 pen.setStrokeWidth(15); 592 pen.setColor({ 593 alpha: 255, 594 red: 255, 595 green: 0, 596 blue: 0 597 }); 598 canvas.attachPen(pen); 599 canvas.drawRect({ 600 left: 50, 601 right: this.width + 50, 602 top: 50, 603 bottom: this.width + 50, 604 }); 605 canvas.detachPen(); 606 } 607 608 addWidth() { 609 this.width = (this.width + 10) % 50 + 100; 610 } 611} 612 613class MyNodeController extends NodeController { 614 public rootNode: MyFrameNode | null = null; 615 616 makeNode(context: UIContext): FrameNode | null { 617 this.rootNode = new MyFrameNode(context); 618 this.rootNode?.commonAttribute?.size({ width: 100, height: 100 }).backgroundColor(Color.Green); 619 let frameNode: FrameNode = new FrameNode(context); 620 this.rootNode.appendChild(frameNode); 621 frameNode.commonAttribute.width(10).height(10).backgroundColor(Color.Pink); 622 return this.rootNode; 623 } 624} 625 626@Entry 627@Component 628struct Index { 629 private nodeController: MyNodeController = new MyNodeController(); 630 631 build() { 632 Row() { 633 Column() { 634 NodeContainer(this.nodeController) 635 .width('100%') 636 .height(200) 637 .backgroundColor('#FFF0F0F0') 638 Button('Invalidate') 639 .margin(10) 640 .onClick(() => { 641 this.nodeController?.rootNode?.addWidth(); 642 this.nodeController?.rootNode?.invalidate(); 643 }) 644 Button('UpdateLayout') 645 .onClick(() => { 646 let node = this.nodeController.rootNode; 647 node!.offsetY = (node!.offsetY + 10) % 110; 648 this.nodeController?.rootNode?.setNeedsLayout(); 649 }) 650 } 651 .width('100%') 652 .height('100%') 653 } 654 .height('100%') 655 } 656} 657``` 658 659## 查找节点及获取基础信息 660 661FrameNode提供了查询接口用于返回实体节点的基础信息。具体返回的信息内容参考FrameNode中提供的接口。 662 663查找获得FrameNode的方式包括三种: 664 6651. 使用[getFrameNodeById](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyid12)获取。 666 6672. 使用[getFrameNodeByUniqueId](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyuniqueid12)获取。 668 6693. 通过[无感监听](../reference/apis-arkui/js-apis-arkui-observer.md)获取。 670 671> **说明:** 672> 673> 1、当前接口提供的可查询的信息包括: 674> 675> - 节点大小:[getMeasuredSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getmeasuredsize12),[getUserConfigSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigsize12) 676> 677> - 布局信息:[getPositionToWindow](../reference/apis-arkui/js-apis-arkui-frameNode.md#getpositiontowindow12),[getPositionToParent](../reference/apis-arkui/js-apis-arkui-frameNode.md#getpositiontoparent12),[getLayoutPosition](../reference/apis-arkui/js-apis-arkui-frameNode.md#getlayoutposition12),[getUserConfigBorderWidth](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigborderwidth12),[getUserConfigPadding](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigpadding12),[getUserConfigMargin](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigmargin12) 678> 679> - 节点信息:[getId](../reference/apis-arkui/js-apis-arkui-frameNode.md#getid12),[getUniqueId](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuniqueid12),[getNodeType](../reference/apis-arkui/js-apis-arkui-frameNode.md#getnodetype12),[getOpacity](../reference/apis-arkui/js-apis-arkui-frameNode.md#getopacity12),[isVisible](../reference/apis-arkui/js-apis-arkui-frameNode.md#isvisible12),[isClipToFrame](../reference/apis-arkui/js-apis-arkui-frameNode.md#iscliptoframe12),[isAttached](../reference/apis-arkui/js-apis-arkui-frameNode.md#isattached12),[getInspectorInfo](../reference/apis-arkui/js-apis-arkui-frameNode.md#getinspectorinfo12),[getCustomProperty](../reference/apis-arkui/js-apis-arkui-frameNode.md#getcustomproperty12) 680> 681> 2、无法获取UINode类型节点,例如: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组件等。 682 683## 获取节点位置偏移信息 684 685FrameNode提供了查询节点相对窗口、父组件以及屏幕位置偏移的信息接口([getPositionToWindow](../reference/apis-arkui/js-apis-arkui-frameNode.md#getpositiontowindow12),[getPositionToParent](../reference/apis-arkui/js-apis-arkui-frameNode.md#getpositiontoparent12),[getPositionToScreen](../reference/apis-arkui/js-apis-arkui-frameNode.md#getpositiontoscreen12),[getPositionToWindowWithTransform](../reference/apis-arkui/js-apis-arkui-frameNode.md#getpositiontowindowwithtransform12),[getPositionToParentWithTransform](../reference/apis-arkui/js-apis-arkui-frameNode.md#getpositiontoparentwithtransform12),[getPositionToScreenWithTransform](../reference/apis-arkui/js-apis-arkui-frameNode.md#getpositiontoscreenwithtransform12),[getLayoutPosition](../reference/apis-arkui/js-apis-arkui-frameNode.md#getlayoutposition12),[getUserConfigBorderWidth](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigborderwidth12),[getUserConfigPadding](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigpadding12),[getUserConfigMargin](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigmargin12))。 686 687```ts 688import { NodeController, FrameNode, UIContext } from '@kit.ArkUI'; 689 690const TEST_TAG: string = "FrameNode" 691 692class MyNodeController extends NodeController { 693 public frameNode: FrameNode | null = null; 694 private rootNode: FrameNode | null = null; 695 696 makeNode(uiContext: UIContext): FrameNode | null { 697 this.rootNode = new FrameNode(uiContext); 698 this.frameNode = new FrameNode(uiContext); 699 this.rootNode.appendChild(this.frameNode); 700 return this.rootNode; 701 } 702 703 getPositionToWindow() { 704 let positionToWindow = this.rootNode?.getPositionToWindow(); // 获取FrameNode相对于窗口的位置偏移 705 console.log(`${TEST_TAG} ${JSON.stringify(positionToWindow)}`); 706 } 707 708 getPositionToParent() { 709 let positionToParent = this.rootNode?.getPositionToParent(); // 获取FrameNode相对于父组件的位置偏移 710 console.log(`${TEST_TAG} ${JSON.stringify(positionToParent)}`); 711 } 712 713 getPositionToScreen() { 714 let positionToScreen = this.rootNode?.getPositionToScreen(); // 获取FrameNode相对于屏幕的位置偏移 715 console.log(`${TEST_TAG} ${JSON.stringify(positionToScreen)}`); 716 } 717 718 getPositionToWindowWithTransform() { 719 let positionToWindowWithTransform = 720 this.rootNode?.getPositionToWindowWithTransform(); // 获取FrameNode相对于窗口带有绘制属性的位置偏移 721 console.log(`${TEST_TAG} ${JSON.stringify(positionToWindowWithTransform)}`); 722 } 723 724 getPositionToParentWithTransform() { 725 let positionToParentWithTransform = 726 this.rootNode?.getPositionToParentWithTransform(); // 获取FrameNode相对于父组件带有绘制属性的位置偏移 727 console.log(`${TEST_TAG} ${JSON.stringify(positionToParentWithTransform)}`); 728 } 729 730 getPositionToScreenWithTransform() { 731 let positionToScreenWithTransform = 732 this.rootNode?.getPositionToScreenWithTransform(); // 获取FrameNode相对于屏幕带有绘制属性的位置偏移 733 console.log(`${TEST_TAG} ${JSON.stringify(positionToScreenWithTransform)}`); 734 } 735} 736 737@Entry 738@Component 739struct Index { 740 private myNodeController: MyNodeController = new MyNodeController(); 741 742 build() { 743 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { 744 Button("getPositionToWindow") 745 .width(300) 746 .onClick(() => { 747 this.myNodeController.getPositionToWindow(); 748 }) 749 Button("getPositionToParent") 750 .width(300) 751 .onClick(() => { 752 this.myNodeController.getPositionToParent(); 753 }) 754 Button("getPositionToScreen") 755 .width(300) 756 .onClick(() => { 757 this.myNodeController.getPositionToScreen(); 758 }) 759 Button("getPositionToParentWithTransform") 760 .width(300) 761 .onClick(() => { 762 this.myNodeController.getPositionToParentWithTransform(); 763 }) 764 Button("getPositionToWindowWithTransform") 765 .width(300) 766 .onClick(() => { 767 this.myNodeController.getPositionToWindowWithTransform(); 768 }) 769 Button("getPositionToScreenWithTransform") 770 .width(300) 771 .onClick(() => { 772 this.myNodeController.getPositionToScreenWithTransform(); 773 }) 774 Column() { 775 Text("This is a NodeContainer.") 776 .textAlign(TextAlign.Center) 777 .borderRadius(10) 778 .backgroundColor(0xFFFFFF) 779 .width('100%') 780 .fontSize(16) 781 NodeContainer(this.myNodeController) 782 .borderWidth(1) 783 .width(300) 784 .height(100) 785 } 786 } 787 .padding({ 788 left: 35, 789 right: 35, 790 top: 35, 791 bottom: 35 792 }) 793 .width("100%") 794 .height("100%") 795 } 796} 797``` 798 799## 通过typeNode创建具体类型的FrameNode节点 800 801通过TypeNode创建具体类型的FrameNode节点,可以根据属性获取接口来检索用户设置的属性信息。 802 803```ts 804import { NodeController, FrameNode, UIContext, BuilderNode, typeNode } from '@kit.ArkUI'; 805 806class Params { 807 text: string = ""; 808 809 constructor(text: string) { 810 this.text = text; 811 } 812} 813 814@Builder 815function buildText(params: Params) { 816 Column() { 817 Text(params.text) 818 .id("buildText") 819 .border({ width: 1 }) 820 .padding(1) 821 .fontSize(25) 822 .fontWeight(FontWeight.Bold) 823 .margin({ top: 10 }) 824 .visibility(Visibility.Visible) 825 .opacity(0.7) 826 .customProperty("key1", "value1") 827 .width(300) 828 } 829} 830 831const TEST_TAG: string = "FrameNode" 832 833class MyNodeController extends NodeController { 834 public frameNode: typeNode.Column | null = null; 835 public uiContext: UIContext | undefined = undefined; 836 private rootNode: FrameNode | null = null; 837 private textNode: BuilderNode<[Params]> | null = null; 838 public textTypeNode: typeNode.Text | null = null; 839 private message: string = "DEFAULT"; 840 841 makeNode(uiContext: UIContext): FrameNode | null { 842 this.rootNode = new FrameNode(uiContext); 843 this.uiContext = uiContext; 844 this.frameNode = typeNode.createNode(uiContext, "Column"); 845 this.frameNode.attribute 846 .width("100%") 847 .height("100%") 848 this.rootNode.appendChild(this.frameNode); 849 this.textNode = new BuilderNode(uiContext); 850 this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message)); 851 this.frameNode.appendChild(this.textNode.getFrameNode()); 852 this.textTypeNode = typeNode.createNode(uiContext, "Text"); 853 this.textTypeNode.initialize("textTypeNode") 854 .fontSize(25) 855 .visibility(Visibility.Visible) 856 .id("textTypeNode") 857 this.frameNode.appendChild(this.textTypeNode); 858 return this.rootNode; 859 } 860 861 removeChild(frameNode: FrameNode) { 862 let parent = frameNode.getParent(); 863 if (parent) { 864 parent.removeChild(frameNode); 865 866 } 867 } 868 869 getUserConfigBorderWidth(frameNode: FrameNode) { 870 let userConfigBorderWidth = frameNode?.getUserConfigBorderWidth(); // 获取用户设置的边框宽度 871 console.log(`${TEST_TAG} ${JSON.stringify(userConfigBorderWidth)}`); 872 } 873 874 getUserConfigPadding(frameNode: FrameNode) { 875 let userConfigPadding = frameNode?.getUserConfigPadding(); // 获取用户设置的内边距 876 console.log(`${TEST_TAG} ${JSON.stringify(userConfigPadding)}`); 877 } 878 879 getUserConfigMargin(frameNode: FrameNode) { 880 let userConfigMargin = frameNode?.getUserConfigMargin(); // 获取用户设置的外边距 881 console.log(`${TEST_TAG} ${JSON.stringify(userConfigMargin)}`); 882 } 883 884 getUserConfigSize(frameNode: FrameNode) { 885 let userConfigSize = frameNode?.getUserConfigSize(); // 获取用户设置的宽高 886 console.log(`${TEST_TAG} ${JSON.stringify(userConfigSize)}`); 887 } 888 889 getId(frameNode: FrameNode) { 890 let id = frameNode?.getId(); // 获取用户设置的节点ID 891 console.log(`${TEST_TAG} ${id}`); 892 } 893 894 getUniqueId(frameNode: FrameNode) { 895 let uniqueId = frameNode?.getUniqueId(); // 获取系统分配的唯一标识的节点UniqueID 896 console.log(`${TEST_TAG} ${uniqueId}`); 897 } 898 899 getNodeType(frameNode: FrameNode) { 900 let nodeType = frameNode?.getNodeType(); // 获取节点的类型 901 console.log(`${TEST_TAG} ${nodeType}`); 902 } 903 904 getOpacity(frameNode: FrameNode) { 905 let opacity = frameNode?.getOpacity(); // 获取节点的不透明度 906 console.log(`${TEST_TAG} ${JSON.stringify(opacity)}`); 907 } 908 909 isVisible(frameNode: FrameNode) { 910 let visible = frameNode?.isVisible(); // 获取节点是否可见 911 console.log(`${TEST_TAG} ${JSON.stringify(visible)}`); 912 } 913 914 isClipToFrame(frameNode: FrameNode) { 915 let clipToFrame = frameNode?.isClipToFrame(); // 获取节点是否是剪裁到组件区域 916 console.log(`${TEST_TAG} ${JSON.stringify(clipToFrame)}`); 917 } 918 919 isAttached(frameNode: FrameNode) { 920 let attached = frameNode?.isAttached(); // 获取节点是否被挂载到主节点树上 921 console.log(`${TEST_TAG} ${JSON.stringify(attached)}`); 922 } 923 924 getInspectorInfo(frameNode: FrameNode) { 925 let inspectorInfo = frameNode?.getInspectorInfo(); // 获取节点的结构信息 926 console.log(`${TEST_TAG} ${JSON.stringify(inspectorInfo)}`); 927 } 928} 929 930@Entry 931@Component 932struct Index { 933 private myNodeController: MyNodeController = new MyNodeController(); 934 @State index: number = 0; 935 936 build() { 937 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { 938 Column() { 939 Text("This is a NodeContainer.") 940 .textAlign(TextAlign.Center) 941 .borderRadius(10) 942 .backgroundColor(0xFFFFFF) 943 .width('100%') 944 .fontSize(16) 945 NodeContainer(this.myNodeController) 946 .borderWidth(1) 947 .width(300) 948 .height(100) 949 } 950 951 Button("getUserConfigBorderWidth") 952 .width(300) 953 .onClick(() => { 954 const uiContext: UIContext = this.getUIContext(); 955 if (uiContext) { 956 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 957 if (node) { 958 this.myNodeController.getUserConfigBorderWidth(node); 959 } 960 } 961 }) 962 Button("getUserConfigPadding") 963 .width(300) 964 .onClick(() => { 965 const uiContext: UIContext = this.getUIContext(); 966 if (uiContext) { 967 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 968 if (node) { 969 this.myNodeController.getUserConfigPadding(node); 970 } 971 } 972 }) 973 Button("getUserConfigMargin") 974 .width(300) 975 .onClick(() => { 976 const uiContext: UIContext = this.getUIContext(); 977 if (uiContext) { 978 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 979 if (node) { 980 this.myNodeController.getUserConfigMargin(node); 981 } 982 } 983 }) 984 Button("getUserConfigSize") 985 .width(300) 986 .onClick(() => { 987 const uiContext: UIContext = this.getUIContext(); 988 if (uiContext) { 989 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 990 if (node) { 991 this.myNodeController.getUserConfigSize(node); 992 } 993 } 994 }) 995 Button("getId") 996 .width(300) 997 .onClick(() => { 998 const uiContext: UIContext = this.getUIContext(); 999 if (uiContext) { 1000 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1001 if (node) { 1002 this.myNodeController.getId(node); 1003 } 1004 } 1005 }) 1006 Button("getUniqueId") 1007 .width(300) 1008 .onClick(() => { 1009 const uiContext: UIContext = this.getUIContext(); 1010 if (uiContext) { 1011 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1012 if (node) { 1013 this.myNodeController.getUniqueId(node); 1014 } 1015 } 1016 }) 1017 Button("getNodeType") 1018 .width(300) 1019 .onClick(() => { 1020 const uiContext: UIContext = this.getUIContext(); 1021 if (uiContext) { 1022 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1023 if (node) { 1024 this.myNodeController.getNodeType(node); 1025 } 1026 } 1027 }) 1028 Button("getOpacity") 1029 .width(300) 1030 .onClick(() => { 1031 const uiContext: UIContext = this.getUIContext(); 1032 if (uiContext) { 1033 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1034 if (node) { 1035 this.myNodeController.getOpacity(node); 1036 } 1037 } 1038 }) 1039 Button("isVisible") 1040 .width(300) 1041 .onClick(() => { 1042 const uiContext: UIContext = this.getUIContext(); 1043 if (uiContext) { 1044 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1045 if (node) { 1046 this.myNodeController.isVisible(node); 1047 } 1048 } 1049 }) 1050 Button("isClipToFrame") 1051 .width(300) 1052 .onClick(() => { 1053 const uiContext: UIContext = this.getUIContext(); 1054 if (uiContext) { 1055 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1056 if (node) { 1057 this.myNodeController.isClipToFrame(node); 1058 } 1059 } 1060 }) 1061 Button("isAttached") 1062 .width(300) 1063 .onClick(() => { 1064 const uiContext: UIContext = this.getUIContext(); 1065 if (uiContext) { 1066 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1067 if (node) { 1068 this.myNodeController.isAttached(node); 1069 } 1070 } 1071 }) 1072 Button("remove Text") 1073 .width(300) 1074 .onClick(() => { 1075 const uiContext: UIContext = this.getUIContext(); 1076 if (uiContext) { 1077 const node: FrameNode | null = uiContext.getFrameNodeById("textTypeNode") || null; 1078 if (node) { 1079 this.myNodeController.removeChild(node); 1080 this.myNodeController.isAttached(node); 1081 } 1082 } 1083 }) 1084 Button("getInspectorInfo") 1085 .width(300) 1086 .onClick(() => { 1087 const uiContext: UIContext = this.getUIContext(); 1088 if (uiContext) { 1089 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1090 if (node) { 1091 this.myNodeController.getInspectorInfo(node); 1092 } 1093 } 1094 }) 1095 Button("getCustomProperty") 1096 .width(300) 1097 .onClick(() => { 1098 const uiContext: UIContext = this.getUIContext(); 1099 if (uiContext) { 1100 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1101 if (node) { 1102 const property = node.getCustomProperty("key1"); 1103 console.log(TEST_TAG, JSON.stringify(property)); 1104 } 1105 } 1106 }) 1107 } 1108 .padding({ 1109 left: 35, 1110 right: 35, 1111 top: 35, 1112 bottom: 35 1113 }) 1114 .width("100%") 1115 .height("100%") 1116 } 1117} 1118``` 1119 1120## 解除当前FrameNode对象对实体FrameNode节点的引用关系 1121 1122使用[dispose](../reference/apis-arkui/js-apis-arkui-frameNode.md#dispose12)接口可以立即解除当前FrameNode对象对实体FrameNode节点的引用关系。 1123 1124> **说明:** 1125> 1126> 在调用dispose方法后,FrameNode对象不再对应任何实际的FrameNode节点。此时,若尝试调用以下查询接口:getMeasuredSize、getLayoutPosition、getUserConfigBorderWidth、getUserConfigPadding、getUserConfigMargin、getUserConfigSize,将导致应用程序触发jscrash。 1127> 1128> 通过[getUniqueId](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuniqueid12)可以判断当前FrameNode是否对应一个实体FrameNode节点。当UniqueId大于0时表示该对象对应一个实体FrameNode节点。 1129 1130```ts 1131import { NodeController, FrameNode, BuilderNode } from '@kit.ArkUI'; 1132 1133const TEST_TAG: string = "FrameNode"; 1134 1135@Component 1136struct TestComponent { 1137 build() { 1138 Column() { 1139 Text('This is a BuilderNode.') 1140 .fontSize(16) 1141 .fontWeight(FontWeight.Bold) 1142 } 1143 .width('100%') 1144 .backgroundColor(Color.Gray) 1145 } 1146 1147 aboutToAppear() { 1148 console.error(TEST_TAG + ' aboutToAppear'); 1149 } 1150 1151 aboutToDisappear() { 1152 console.error(TEST_TAG + ' aboutToDisappear'); 1153 } 1154} 1155 1156@Builder 1157function buildComponent() { 1158 TestComponent() 1159} 1160 1161class MyNodeController extends NodeController { 1162 private rootNode: FrameNode | null = null; 1163 private builderNode: BuilderNode<[]> | null = null; 1164 1165 makeNode(uiContext: UIContext): FrameNode | null { 1166 this.rootNode = new FrameNode(uiContext); 1167 this.builderNode = new BuilderNode(uiContext, { selfIdealSize: { width: 200, height: 100 } }); 1168 this.builderNode.build(new WrappedBuilder(buildComponent)); 1169 1170 const rootRenderNode = this.rootNode.getRenderNode(); 1171 if (rootRenderNode !== null) { 1172 rootRenderNode.size = { width: 200, height: 200 }; 1173 rootRenderNode.backgroundColor = 0xff00ff00; 1174 rootRenderNode.appendChild(this.builderNode!.getFrameNode()!.getRenderNode()); 1175 } 1176 1177 return this.rootNode; 1178 } 1179 1180 printUniqueId(): void { 1181 if (this.rootNode !== null && this.builderNode !== null) { 1182 console.log(`${TEST_TAG} rootNode's uniqueId: ${this.rootNode.getUniqueId()}`); 1183 const frameNode = this.builderNode.getFrameNode(); 1184 if (frameNode) { 1185 console.log(`${TEST_TAG} the uniqueId of builderNode's framenode: ${frameNode.getUniqueId()}`); 1186 } else { 1187 console.log(`${TEST_TAG} builderNode's framenode is undefined`); 1188 } 1189 } 1190 } 1191 1192 disposeFrameNode(): void { 1193 if (this.rootNode !== null && this.builderNode !== null) { 1194 console.log(`${TEST_TAG} disposeFrameNode`); 1195 this.rootNode.removeChild(this.builderNode.getFrameNode()); 1196 this.builderNode.dispose(); 1197 1198 this.rootNode.dispose(); 1199 } 1200 } 1201 1202 removeBuilderNode(): void { 1203 const rootRenderNode = this.rootNode!.getRenderNode(); 1204 if (rootRenderNode !== null && this.builderNode !== null && this.builderNode.getFrameNode() !== null) { 1205 rootRenderNode.removeChild(this.builderNode!.getFrameNode()!.getRenderNode()); 1206 } 1207 } 1208} 1209 1210@Entry 1211@Component 1212struct Index { 1213 private myNodeController: MyNodeController = new MyNodeController(); 1214 1215 build() { 1216 Column({ space: 4 }) { 1217 NodeContainer(this.myNodeController) 1218 Button('FrameNode dispose') 1219 .onClick(() => { 1220 this.myNodeController.printUniqueId(); 1221 this.myNodeController.disposeFrameNode(); 1222 this.myNodeController.printUniqueId(); 1223 }) 1224 .width('100%') 1225 } 1226 } 1227} 1228``` 1229 1230## FrameNode的数据懒加载能力 1231 1232提供[NodeAdapter](../reference/apis-arkui/js-apis-arkui-frameNode.md#nodeadapter12)对象替代ArkTS侧的LazyForEach功能,提供自定义节点的数据懒加载功能,实现按需迭代数据。 1233 1234> **说明:** 1235> 1236> 入参不能为负数,入参为负数时不做处理。 1237 1238```ts 1239import { FrameNode, NodeController, NodeAdapter, typeNode } from '@kit.ArkUI'; 1240 1241const TEST_TAG: string = "FrameNode"; 1242 1243class MyNodeAdapter extends NodeAdapter { 1244 uiContext: UIContext 1245 cachePool: Array<FrameNode> = new Array(); 1246 changed: boolean = false 1247 reloadTimes: number = 0; 1248 data: Array<string> = new Array(); 1249 hostNode?: FrameNode 1250 1251 constructor(uiContext: UIContext, count: number) { 1252 super(); 1253 this.uiContext = uiContext; 1254 this.totalNodeCount = count; 1255 this.loadData(); 1256 } 1257 1258 reloadData(count: number): void { 1259 this.reloadTimes++; 1260 NodeAdapter.attachNodeAdapter(this, this.hostNode); 1261 this.totalNodeCount = count; 1262 this.loadData(); 1263 this.reloadAllItems(); 1264 } 1265 1266 refreshData(): void { 1267 let items = this.getAllAvailableItems() 1268 console.log(TEST_TAG + " get All items:" + items.length); 1269 this.totalNodeCount -= 1; 1270 this.reloadAllItems(); 1271 } 1272 1273 detachData(): void { 1274 NodeAdapter.detachNodeAdapter(this.hostNode); 1275 this.reloadTimes = 0; 1276 } 1277 1278 loadData(): void { 1279 for (let i = 0; i < this.totalNodeCount; i++) { 1280 this.data[i] = "Adapter ListItem " + i + " r:" + this.reloadTimes; 1281 } 1282 } 1283 1284 changeData(from: number, count: number): void { 1285 this.changed = !this.changed; 1286 for (let i = 0; i < count; i++) { 1287 let index = i + from; 1288 this.data[index] = "Adapter ListItem " + (this.changed ? "changed:" : "") + index + " r:" + this.reloadTimes; 1289 } 1290 this.reloadItem(from, count); 1291 } 1292 1293 insertData(from: number, count: number): void { 1294 for (let i = 0; i < count; i++) { 1295 let index = i + from; 1296 this.data.splice(index, 0, "Adapter ListItem " + from + "-" + i); 1297 } 1298 this.insertItem(from, count); 1299 this.totalNodeCount += count; 1300 console.log(TEST_TAG + " after insert count:" + this.totalNodeCount); 1301 } 1302 1303 removeData(from: number, count: number): void { 1304 let arr = this.data.splice(from, count); 1305 this.removeItem(from, count); 1306 this.totalNodeCount -= arr.length; 1307 console.log(TEST_TAG + " after remove count:" + this.totalNodeCount); 1308 } 1309 1310 moveData(from: number, to: number): void { 1311 let tmp = this.data.splice(from, 1); 1312 this.data.splice(to, 0, tmp[0]); 1313 this.moveItem(from, to); 1314 } 1315 1316 onAttachToNode(target: FrameNode): void { 1317 console.log(TEST_TAG + " onAttachToNode id:" + target.getUniqueId()); 1318 this.hostNode = target; 1319 } 1320 1321 onDetachFromNode(): void { 1322 console.log(TEST_TAG + " onDetachFromNode"); 1323 } 1324 1325 onGetChildId(index: number): number { 1326 console.log(TEST_TAG + " onGetChildId:" + index); 1327 return index; 1328 } 1329 1330 onCreateChild(index: number): FrameNode { 1331 console.log(TEST_TAG + " onCreateChild:" + index); 1332 if (this.cachePool.length > 0) { 1333 let cacheNode = this.cachePool.pop(); 1334 if (cacheNode !== undefined) { 1335 console.log(TEST_TAG + " onCreateChild reused id:" + cacheNode.getUniqueId()); 1336 let text = cacheNode?.getFirstChild(); 1337 let textNode = text as typeNode.Text; 1338 textNode?.initialize(this.data[index]).fontSize(20); 1339 return cacheNode; 1340 } 1341 } 1342 console.log(TEST_TAG + " onCreateChild createNew"); 1343 let itemNode = typeNode.createNode(this.uiContext, "ListItem"); 1344 let textNode = typeNode.createNode(this.uiContext, "Text"); 1345 textNode.initialize(this.data[index]).fontSize(20); 1346 itemNode.appendChild(textNode); 1347 return itemNode; 1348 } 1349 1350 onDisposeChild(id: number, node: FrameNode): void { 1351 console.log(TEST_TAG + " onDisposeChild:" + id); 1352 if (this.cachePool.length < 10) { 1353 if (!this.cachePool.includes(node)) { 1354 console.log(TEST_TAG + " caching node id:" + node.getUniqueId()); 1355 this.cachePool.push(node); 1356 } 1357 } else { 1358 node.dispose(); 1359 } 1360 } 1361 1362 onUpdateChild(id: number, node: FrameNode): void { 1363 let index = id; 1364 let text = node.getFirstChild(); 1365 let textNode = text as typeNode.Text; 1366 textNode?.initialize(this.data[index]).fontSize(20); 1367 } 1368} 1369 1370class MyNodeAdapterController extends NodeController { 1371 rootNode: FrameNode | null = null; 1372 nodeAdapter: MyNodeAdapter | null = null; 1373 1374 makeNode(uiContext: UIContext): FrameNode | null { 1375 this.rootNode = new FrameNode(uiContext); 1376 let listNode = typeNode.createNode(uiContext, "List"); 1377 listNode.initialize({ space: 3 }).borderWidth(2).borderColor(Color.Black); 1378 this.rootNode.appendChild(listNode); 1379 this.nodeAdapter = new MyNodeAdapter(uiContext, 100); 1380 NodeAdapter.attachNodeAdapter(this.nodeAdapter, listNode); 1381 return this.rootNode; 1382 } 1383} 1384 1385@Entry 1386@Component 1387struct ListNodeTest { 1388 adapterController: MyNodeAdapterController = new MyNodeAdapterController(); 1389 1390 build() { 1391 Column() { 1392 Text("ListNode Adapter"); 1393 NodeContainer(this.adapterController) 1394 .width(300).height(300) 1395 .borderWidth(1).borderColor(Color.Black); 1396 Row() { 1397 Button("Reload") 1398 .onClick(() => { 1399 this.adapterController.nodeAdapter?.reloadData(50); 1400 }) 1401 Button("Change") 1402 .onClick(() => { 1403 this.adapterController.nodeAdapter?.changeData(5, 10) 1404 }) 1405 Button("Insert") 1406 .onClick(() => { 1407 this.adapterController.nodeAdapter?.insertData(10, 10); 1408 }) 1409 } 1410 1411 Row() { 1412 Button("Remove") 1413 .onClick(() => { 1414 this.adapterController.nodeAdapter?.removeData(10, 10); 1415 }) 1416 Button("Move") 1417 .onClick(() => { 1418 this.adapterController.nodeAdapter?.moveData(2, 5); 1419 }) 1420 Button("Refresh") 1421 .onClick(() => { 1422 this.adapterController.nodeAdapter?.refreshData(); 1423 }) 1424 Button("Detach") 1425 .onClick(() => { 1426 this.adapterController.nodeAdapter?.detachData(); 1427 }) 1428 } 1429 }.borderWidth(1) 1430 .width("100%") 1431 } 1432} 1433``` 1434