1# 自定义组件节点 (FrameNode) 2 3## 概述 4 5对于拥有自定义前端的第三方框架(如JSON、XML、DOM树等),需将特定的DSL转换为ArkUI的声明式描述。如下图描述了JSON定义的前端框架和ArkUI声明式描述的对应关系。 6 7 8 9上述转换过程需要依赖额外的数据驱动,绑定至[Builder](../ui/state-management/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 uiContext: UIContext; 558 559 constructor(uiContext: UIContext) { 560 super(uiContext); 561 this.uiContext = uiContext; 562 } 563 564 onMeasure(constraint: LayoutConstraint): void { 565 let sizeRes: Size = { width: this.uiContext.vp2px(100), height: this.uiContext.vp2px(100) }; 566 for (let i = 0; i < this.getChildrenCount(); i++) { 567 let child = this.getChild(i); 568 if (child) { 569 let childConstraint = GetChildLayoutConstraint(constraint, child); 570 child.measure(childConstraint); 571 let size = child.getMeasuredSize(); 572 sizeRes.height += size.height + this.space; 573 sizeRes.width = Math.max(sizeRes.width, size.width); 574 } 575 } 576 this.setMeasuredSize(sizeRes); 577 } 578 579 onLayout(position: Position): void { 580 for (let i = 0; i < this.getChildrenCount(); i++) { 581 let child = this.getChild(i); 582 if (child) { 583 child.layout({ 584 x: this.uiContext.vp2px(100), 585 y: this.uiContext.vp2px(this.offsetY) 586 }); 587 let layoutPosition = child.getLayoutPosition(); 588 console.log("child position:" + JSON.stringify(layoutPosition)); 589 } 590 } 591 this.setLayoutPosition(position); 592 } 593 594 onDraw(context: DrawContext) { 595 const canvas = context.canvas; 596 const pen = new drawing.Pen(); 597 pen.setStrokeWidth(15); 598 pen.setColor({ 599 alpha: 255, 600 red: 255, 601 green: 0, 602 blue: 0 603 }); 604 canvas.attachPen(pen); 605 canvas.drawRect({ 606 left: 50, 607 right: this.width + 50, 608 top: 50, 609 bottom: this.width + 50, 610 }); 611 canvas.detachPen(); 612 } 613 614 addWidth() { 615 this.width = (this.width + 10) % 50 + 100; 616 } 617} 618 619class MyNodeController extends NodeController { 620 public rootNode: MyFrameNode | null = null; 621 622 makeNode(context: UIContext): FrameNode | null { 623 this.rootNode = new MyFrameNode(context); 624 this.rootNode?.commonAttribute?.size({ width: 100, height: 100 }).backgroundColor(Color.Green); 625 let frameNode: FrameNode = new FrameNode(context); 626 this.rootNode.appendChild(frameNode); 627 frameNode.commonAttribute.width(10).height(10).backgroundColor(Color.Pink); 628 return this.rootNode; 629 } 630} 631 632@Entry 633@Component 634struct Index { 635 private nodeController: MyNodeController = new MyNodeController(); 636 637 build() { 638 Row() { 639 Column() { 640 NodeContainer(this.nodeController) 641 .width('100%') 642 .height(200) 643 .backgroundColor('#FFF0F0F0') 644 Button('Invalidate') 645 .margin(10) 646 .onClick(() => { 647 this.nodeController?.rootNode?.addWidth(); 648 this.nodeController?.rootNode?.invalidate(); 649 }) 650 Button('UpdateLayout') 651 .onClick(() => { 652 let node = this.nodeController.rootNode; 653 node!.offsetY = (node!.offsetY + 10) % 110; 654 this.nodeController?.rootNode?.setNeedsLayout(); 655 }) 656 } 657 .width('100%') 658 .height('100%') 659 } 660 .height('100%') 661 } 662} 663``` 664 665## 查找节点及获取基础信息 666 667FrameNode提供了查询接口用于返回实体节点的基础信息。具体返回的信息内容参考FrameNode中提供的接口。 668 669查找获得FrameNode的方式包括三种: 670 6711. 使用[getFrameNodeById](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyid12)获取。 672 6732. 使用[getFrameNodeByUniqueId](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyuniqueid12)获取。 674 6753. 通过[无感监听](../reference/apis-arkui/js-apis-arkui-observer.md)获取。 676 677> **说明:** 678> 679> 1、当前接口提供的可查询的信息包括: 680> 681> - 节点大小:[getMeasuredSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getmeasuredsize12),[getUserConfigSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigsize12) 682> 683> - 布局信息:[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) 684> 685> - 节点信息:[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) 686> 687> 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组件等。 688 689## 获取节点位置偏移信息 690 691FrameNode提供了查询节点相对窗口、父组件以及屏幕位置偏移的信息接口([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))。 692 693```ts 694import { NodeController, FrameNode, UIContext } from '@kit.ArkUI'; 695 696const TEST_TAG: string = "FrameNode" 697 698class MyNodeController extends NodeController { 699 public frameNode: FrameNode | null = null; 700 private rootNode: FrameNode | null = null; 701 702 makeNode(uiContext: UIContext): FrameNode | null { 703 this.rootNode = new FrameNode(uiContext); 704 this.frameNode = new FrameNode(uiContext); 705 this.rootNode.appendChild(this.frameNode); 706 return this.rootNode; 707 } 708 709 getPositionToWindow() { 710 let positionToWindow = this.rootNode?.getPositionToWindow(); // 获取FrameNode相对于窗口的位置偏移 711 console.log(`${TEST_TAG} ${JSON.stringify(positionToWindow)}`); 712 } 713 714 getPositionToParent() { 715 let positionToParent = this.rootNode?.getPositionToParent(); // 获取FrameNode相对于父组件的位置偏移 716 console.log(`${TEST_TAG} ${JSON.stringify(positionToParent)}`); 717 } 718 719 getPositionToScreen() { 720 let positionToScreen = this.rootNode?.getPositionToScreen(); // 获取FrameNode相对于屏幕的位置偏移 721 console.log(`${TEST_TAG} ${JSON.stringify(positionToScreen)}`); 722 } 723 724 getPositionToWindowWithTransform() { 725 let positionToWindowWithTransform = 726 this.rootNode?.getPositionToWindowWithTransform(); // 获取FrameNode相对于窗口带有绘制属性的位置偏移 727 console.log(`${TEST_TAG} ${JSON.stringify(positionToWindowWithTransform)}`); 728 } 729 730 getPositionToParentWithTransform() { 731 let positionToParentWithTransform = 732 this.rootNode?.getPositionToParentWithTransform(); // 获取FrameNode相对于父组件带有绘制属性的位置偏移 733 console.log(`${TEST_TAG} ${JSON.stringify(positionToParentWithTransform)}`); 734 } 735 736 getPositionToScreenWithTransform() { 737 let positionToScreenWithTransform = 738 this.rootNode?.getPositionToScreenWithTransform(); // 获取FrameNode相对于屏幕带有绘制属性的位置偏移 739 console.log(`${TEST_TAG} ${JSON.stringify(positionToScreenWithTransform)}`); 740 } 741} 742 743@Entry 744@Component 745struct Index { 746 private myNodeController: MyNodeController = new MyNodeController(); 747 748 build() { 749 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { 750 Button("getPositionToWindow") 751 .width(300) 752 .onClick(() => { 753 this.myNodeController.getPositionToWindow(); 754 }) 755 Button("getPositionToParent") 756 .width(300) 757 .onClick(() => { 758 this.myNodeController.getPositionToParent(); 759 }) 760 Button("getPositionToScreen") 761 .width(300) 762 .onClick(() => { 763 this.myNodeController.getPositionToScreen(); 764 }) 765 Button("getPositionToParentWithTransform") 766 .width(300) 767 .onClick(() => { 768 this.myNodeController.getPositionToParentWithTransform(); 769 }) 770 Button("getPositionToWindowWithTransform") 771 .width(300) 772 .onClick(() => { 773 this.myNodeController.getPositionToWindowWithTransform(); 774 }) 775 Button("getPositionToScreenWithTransform") 776 .width(300) 777 .onClick(() => { 778 this.myNodeController.getPositionToScreenWithTransform(); 779 }) 780 Column() { 781 Text("This is a NodeContainer.") 782 .textAlign(TextAlign.Center) 783 .borderRadius(10) 784 .backgroundColor(0xFFFFFF) 785 .width('100%') 786 .fontSize(16) 787 NodeContainer(this.myNodeController) 788 .borderWidth(1) 789 .width(300) 790 .height(100) 791 } 792 } 793 .padding({ 794 left: 35, 795 right: 35, 796 top: 35, 797 bottom: 35 798 }) 799 .width("100%") 800 .height("100%") 801 } 802} 803``` 804 805## 通过typeNode创建具体类型的FrameNode节点 806 807通过TypeNode创建具体类型的FrameNode节点,可以根据属性获取接口来检索用户设置的属性信息。 808 809```ts 810import { NodeController, FrameNode, UIContext, BuilderNode, typeNode } from '@kit.ArkUI'; 811 812class Params { 813 text: string = ""; 814 815 constructor(text: string) { 816 this.text = text; 817 } 818} 819 820@Builder 821function buildText(params: Params) { 822 Column() { 823 Text(params.text) 824 .id("buildText") 825 .border({ width: 1 }) 826 .padding(1) 827 .fontSize(25) 828 .fontWeight(FontWeight.Bold) 829 .margin({ top: 10 }) 830 .visibility(Visibility.Visible) 831 .opacity(0.7) 832 .customProperty("key1", "value1") 833 .width(300) 834 } 835} 836 837const TEST_TAG: string = "FrameNode" 838 839class MyNodeController extends NodeController { 840 public frameNode: typeNode.Column | null = null; 841 public uiContext: UIContext | undefined = undefined; 842 private rootNode: FrameNode | null = null; 843 private textNode: BuilderNode<[Params]> | null = null; 844 public textTypeNode: typeNode.Text | null = null; 845 private message: string = "DEFAULT"; 846 847 makeNode(uiContext: UIContext): FrameNode | null { 848 this.rootNode = new FrameNode(uiContext); 849 this.uiContext = uiContext; 850 this.frameNode = typeNode.createNode(uiContext, "Column"); 851 this.frameNode.attribute 852 .width("100%") 853 .height("100%") 854 this.rootNode.appendChild(this.frameNode); 855 this.textNode = new BuilderNode(uiContext); 856 this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message)); 857 this.frameNode.appendChild(this.textNode.getFrameNode()); 858 this.textTypeNode = typeNode.createNode(uiContext, "Text"); 859 this.textTypeNode.initialize("textTypeNode") 860 .fontSize(25) 861 .visibility(Visibility.Visible) 862 .id("textTypeNode") 863 this.frameNode.appendChild(this.textTypeNode); 864 return this.rootNode; 865 } 866 867 removeChild(frameNode: FrameNode) { 868 let parent = frameNode.getParent(); 869 if (parent) { 870 parent.removeChild(frameNode); 871 872 } 873 } 874 875 getUserConfigBorderWidth(frameNode: FrameNode) { 876 let userConfigBorderWidth = frameNode?.getUserConfigBorderWidth(); // 获取用户设置的边框宽度 877 console.log(`${TEST_TAG} ${JSON.stringify(userConfigBorderWidth)}`); 878 } 879 880 getUserConfigPadding(frameNode: FrameNode) { 881 let userConfigPadding = frameNode?.getUserConfigPadding(); // 获取用户设置的内边距 882 console.log(`${TEST_TAG} ${JSON.stringify(userConfigPadding)}`); 883 } 884 885 getUserConfigMargin(frameNode: FrameNode) { 886 let userConfigMargin = frameNode?.getUserConfigMargin(); // 获取用户设置的外边距 887 console.log(`${TEST_TAG} ${JSON.stringify(userConfigMargin)}`); 888 } 889 890 getUserConfigSize(frameNode: FrameNode) { 891 let userConfigSize = frameNode?.getUserConfigSize(); // 获取用户设置的宽高 892 console.log(`${TEST_TAG} ${JSON.stringify(userConfigSize)}`); 893 } 894 895 getId(frameNode: FrameNode) { 896 let id = frameNode?.getId(); // 获取用户设置的节点ID 897 console.log(`${TEST_TAG} ${id}`); 898 } 899 900 getUniqueId(frameNode: FrameNode) { 901 let uniqueId = frameNode?.getUniqueId(); // 获取系统分配的唯一标识的节点UniqueID 902 console.log(`${TEST_TAG} ${uniqueId}`); 903 } 904 905 getNodeType(frameNode: FrameNode) { 906 let nodeType = frameNode?.getNodeType(); // 获取节点的类型 907 console.log(`${TEST_TAG} ${nodeType}`); 908 } 909 910 getOpacity(frameNode: FrameNode) { 911 let opacity = frameNode?.getOpacity(); // 获取节点的不透明度 912 console.log(`${TEST_TAG} ${JSON.stringify(opacity)}`); 913 } 914 915 isVisible(frameNode: FrameNode) { 916 let visible = frameNode?.isVisible(); // 获取节点是否可见 917 console.log(`${TEST_TAG} ${JSON.stringify(visible)}`); 918 } 919 920 isClipToFrame(frameNode: FrameNode) { 921 let clipToFrame = frameNode?.isClipToFrame(); // 获取节点是否是剪裁到组件区域 922 console.log(`${TEST_TAG} ${JSON.stringify(clipToFrame)}`); 923 } 924 925 isAttached(frameNode: FrameNode) { 926 let attached = frameNode?.isAttached(); // 获取节点是否被挂载到主节点树上 927 console.log(`${TEST_TAG} ${JSON.stringify(attached)}`); 928 } 929 930 getInspectorInfo(frameNode: FrameNode) { 931 let inspectorInfo = frameNode?.getInspectorInfo(); // 获取节点的结构信息 932 console.log(`${TEST_TAG} ${JSON.stringify(inspectorInfo)}`); 933 } 934} 935 936@Entry 937@Component 938struct Index { 939 private myNodeController: MyNodeController = new MyNodeController(); 940 @State index: number = 0; 941 942 build() { 943 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { 944 Column() { 945 Text("This is a NodeContainer.") 946 .textAlign(TextAlign.Center) 947 .borderRadius(10) 948 .backgroundColor(0xFFFFFF) 949 .width('100%') 950 .fontSize(16) 951 NodeContainer(this.myNodeController) 952 .borderWidth(1) 953 .width(300) 954 .height(100) 955 } 956 957 Button("getUserConfigBorderWidth") 958 .width(300) 959 .onClick(() => { 960 const uiContext: UIContext = this.getUIContext(); 961 if (uiContext) { 962 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 963 if (node) { 964 this.myNodeController.getUserConfigBorderWidth(node); 965 } 966 } 967 }) 968 Button("getUserConfigPadding") 969 .width(300) 970 .onClick(() => { 971 const uiContext: UIContext = this.getUIContext(); 972 if (uiContext) { 973 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 974 if (node) { 975 this.myNodeController.getUserConfigPadding(node); 976 } 977 } 978 }) 979 Button("getUserConfigMargin") 980 .width(300) 981 .onClick(() => { 982 const uiContext: UIContext = this.getUIContext(); 983 if (uiContext) { 984 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 985 if (node) { 986 this.myNodeController.getUserConfigMargin(node); 987 } 988 } 989 }) 990 Button("getUserConfigSize") 991 .width(300) 992 .onClick(() => { 993 const uiContext: UIContext = this.getUIContext(); 994 if (uiContext) { 995 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 996 if (node) { 997 this.myNodeController.getUserConfigSize(node); 998 } 999 } 1000 }) 1001 Button("getId") 1002 .width(300) 1003 .onClick(() => { 1004 const uiContext: UIContext = this.getUIContext(); 1005 if (uiContext) { 1006 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1007 if (node) { 1008 this.myNodeController.getId(node); 1009 } 1010 } 1011 }) 1012 Button("getUniqueId") 1013 .width(300) 1014 .onClick(() => { 1015 const uiContext: UIContext = this.getUIContext(); 1016 if (uiContext) { 1017 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1018 if (node) { 1019 this.myNodeController.getUniqueId(node); 1020 } 1021 } 1022 }) 1023 Button("getNodeType") 1024 .width(300) 1025 .onClick(() => { 1026 const uiContext: UIContext = this.getUIContext(); 1027 if (uiContext) { 1028 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1029 if (node) { 1030 this.myNodeController.getNodeType(node); 1031 } 1032 } 1033 }) 1034 Button("getOpacity") 1035 .width(300) 1036 .onClick(() => { 1037 const uiContext: UIContext = this.getUIContext(); 1038 if (uiContext) { 1039 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1040 if (node) { 1041 this.myNodeController.getOpacity(node); 1042 } 1043 } 1044 }) 1045 Button("isVisible") 1046 .width(300) 1047 .onClick(() => { 1048 const uiContext: UIContext = this.getUIContext(); 1049 if (uiContext) { 1050 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1051 if (node) { 1052 this.myNodeController.isVisible(node); 1053 } 1054 } 1055 }) 1056 Button("isClipToFrame") 1057 .width(300) 1058 .onClick(() => { 1059 const uiContext: UIContext = this.getUIContext(); 1060 if (uiContext) { 1061 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1062 if (node) { 1063 this.myNodeController.isClipToFrame(node); 1064 } 1065 } 1066 }) 1067 Button("isAttached") 1068 .width(300) 1069 .onClick(() => { 1070 const uiContext: UIContext = this.getUIContext(); 1071 if (uiContext) { 1072 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1073 if (node) { 1074 this.myNodeController.isAttached(node); 1075 } 1076 } 1077 }) 1078 Button("remove Text") 1079 .width(300) 1080 .onClick(() => { 1081 const uiContext: UIContext = this.getUIContext(); 1082 if (uiContext) { 1083 const node: FrameNode | null = uiContext.getFrameNodeById("textTypeNode") || null; 1084 if (node) { 1085 this.myNodeController.removeChild(node); 1086 this.myNodeController.isAttached(node); 1087 } 1088 } 1089 }) 1090 Button("getInspectorInfo") 1091 .width(300) 1092 .onClick(() => { 1093 const uiContext: UIContext = this.getUIContext(); 1094 if (uiContext) { 1095 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1096 if (node) { 1097 this.myNodeController.getInspectorInfo(node); 1098 } 1099 } 1100 }) 1101 Button("getCustomProperty") 1102 .width(300) 1103 .onClick(() => { 1104 const uiContext: UIContext = this.getUIContext(); 1105 if (uiContext) { 1106 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1107 if (node) { 1108 const property = node.getCustomProperty("key1"); 1109 console.log(TEST_TAG, JSON.stringify(property)); 1110 } 1111 } 1112 }) 1113 } 1114 .padding({ 1115 left: 35, 1116 right: 35, 1117 top: 35, 1118 bottom: 35 1119 }) 1120 .width("100%") 1121 .height("100%") 1122 } 1123} 1124``` 1125 1126## 解除当前FrameNode对象对实体FrameNode节点的引用关系 1127 1128使用[dispose](../reference/apis-arkui/js-apis-arkui-frameNode.md#dispose12)接口可以立即解除当前FrameNode对象对实体FrameNode节点的引用关系。 1129 1130> **说明:** 1131> 1132> 在调用dispose方法后,FrameNode对象不再对应任何实际的FrameNode节点。此时,若尝试调用以下查询接口:getMeasuredSize、getLayoutPosition、getUserConfigBorderWidth、getUserConfigPadding、getUserConfigMargin、getUserConfigSize,将导致应用程序触发jscrash。 1133> 1134> 通过[getUniqueId](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuniqueid12)可以判断当前FrameNode是否对应一个实体FrameNode节点。当UniqueId大于0时表示该对象对应一个实体FrameNode节点。 1135 1136```ts 1137import { NodeController, FrameNode, BuilderNode } from '@kit.ArkUI'; 1138 1139const TEST_TAG: string = "FrameNode"; 1140 1141@Component 1142struct TestComponent { 1143 build() { 1144 Column() { 1145 Text('This is a BuilderNode.') 1146 .fontSize(16) 1147 .fontWeight(FontWeight.Bold) 1148 } 1149 .width('100%') 1150 .backgroundColor(Color.Gray) 1151 } 1152 1153 aboutToAppear() { 1154 console.error(TEST_TAG + ' aboutToAppear'); 1155 } 1156 1157 aboutToDisappear() { 1158 console.error(TEST_TAG + ' aboutToDisappear'); 1159 } 1160} 1161 1162@Builder 1163function buildComponent() { 1164 TestComponent() 1165} 1166 1167class MyNodeController extends NodeController { 1168 private rootNode: FrameNode | null = null; 1169 private builderNode: BuilderNode<[]> | null = null; 1170 1171 makeNode(uiContext: UIContext): FrameNode | null { 1172 this.rootNode = new FrameNode(uiContext); 1173 this.builderNode = new BuilderNode(uiContext, { selfIdealSize: { width: 200, height: 100 } }); 1174 this.builderNode.build(new WrappedBuilder(buildComponent)); 1175 1176 const rootRenderNode = this.rootNode.getRenderNode(); 1177 if (rootRenderNode !== null) { 1178 rootRenderNode.size = { width: 200, height: 200 }; 1179 rootRenderNode.backgroundColor = 0xff00ff00; 1180 rootRenderNode.appendChild(this.builderNode!.getFrameNode()!.getRenderNode()); 1181 } 1182 1183 return this.rootNode; 1184 } 1185 1186 printUniqueId(): void { 1187 if (this.rootNode !== null && this.builderNode !== null) { 1188 console.log(`${TEST_TAG} rootNode's uniqueId: ${this.rootNode.getUniqueId()}`); 1189 const frameNode = this.builderNode.getFrameNode(); 1190 if (frameNode) { 1191 console.log(`${TEST_TAG} the uniqueId of builderNode's framenode: ${frameNode.getUniqueId()}`); 1192 } else { 1193 console.log(`${TEST_TAG} builderNode's framenode is undefined`); 1194 } 1195 } 1196 } 1197 1198 disposeFrameNode(): void { 1199 if (this.rootNode !== null && this.builderNode !== null) { 1200 console.log(`${TEST_TAG} disposeFrameNode`); 1201 this.rootNode.removeChild(this.builderNode.getFrameNode()); 1202 this.builderNode.dispose(); 1203 1204 this.rootNode.dispose(); 1205 } 1206 } 1207 1208 removeBuilderNode(): void { 1209 const rootRenderNode = this.rootNode!.getRenderNode(); 1210 if (rootRenderNode !== null && this.builderNode !== null && this.builderNode.getFrameNode() !== null) { 1211 rootRenderNode.removeChild(this.builderNode!.getFrameNode()!.getRenderNode()); 1212 } 1213 } 1214} 1215 1216@Entry 1217@Component 1218struct Index { 1219 private myNodeController: MyNodeController = new MyNodeController(); 1220 1221 build() { 1222 Column({ space: 4 }) { 1223 NodeContainer(this.myNodeController) 1224 Button('FrameNode dispose') 1225 .onClick(() => { 1226 this.myNodeController.printUniqueId(); 1227 this.myNodeController.disposeFrameNode(); 1228 this.myNodeController.printUniqueId(); 1229 }) 1230 .width('100%') 1231 } 1232 } 1233} 1234``` 1235 1236## FrameNode的数据懒加载能力 1237 1238提供[NodeAdapter](../reference/apis-arkui/js-apis-arkui-frameNode.md#nodeadapter12)对象替代ArkTS侧的LazyForEach功能,提供自定义节点的数据懒加载功能,实现按需迭代数据。 1239 1240> **说明:** 1241> 1242> 入参不能为负数,入参为负数时不做处理。 1243 1244```ts 1245import { FrameNode, NodeController, NodeAdapter, typeNode } from '@kit.ArkUI'; 1246 1247const TEST_TAG: string = "FrameNode"; 1248 1249class MyNodeAdapter extends NodeAdapter { 1250 uiContext: UIContext 1251 cachePool: Array<FrameNode> = new Array(); 1252 changed: boolean = false 1253 reloadTimes: number = 0; 1254 data: Array<string> = new Array(); 1255 hostNode?: FrameNode 1256 1257 constructor(uiContext: UIContext, count: number) { 1258 super(); 1259 this.uiContext = uiContext; 1260 this.totalNodeCount = count; 1261 this.loadData(); 1262 } 1263 1264 reloadData(count: number): void { 1265 this.reloadTimes++; 1266 NodeAdapter.attachNodeAdapter(this, this.hostNode); 1267 this.totalNodeCount = count; 1268 this.loadData(); 1269 this.reloadAllItems(); 1270 } 1271 1272 refreshData(): void { 1273 let items = this.getAllAvailableItems() 1274 console.log(TEST_TAG + " get All items:" + items.length); 1275 this.totalNodeCount -= 1; 1276 this.reloadAllItems(); 1277 } 1278 1279 detachData(): void { 1280 NodeAdapter.detachNodeAdapter(this.hostNode); 1281 this.reloadTimes = 0; 1282 } 1283 1284 loadData(): void { 1285 for (let i = 0; i < this.totalNodeCount; i++) { 1286 this.data[i] = "Adapter ListItem " + i + " r:" + this.reloadTimes; 1287 } 1288 } 1289 1290 changeData(from: number, count: number): void { 1291 this.changed = !this.changed; 1292 for (let i = 0; i < count; i++) { 1293 let index = i + from; 1294 this.data[index] = "Adapter ListItem " + (this.changed ? "changed:" : "") + index + " r:" + this.reloadTimes; 1295 } 1296 this.reloadItem(from, count); 1297 } 1298 1299 insertData(from: number, count: number): void { 1300 for (let i = 0; i < count; i++) { 1301 let index = i + from; 1302 this.data.splice(index, 0, "Adapter ListItem " + from + "-" + i); 1303 } 1304 this.insertItem(from, count); 1305 this.totalNodeCount += count; 1306 console.log(TEST_TAG + " after insert count:" + this.totalNodeCount); 1307 } 1308 1309 removeData(from: number, count: number): void { 1310 let arr = this.data.splice(from, count); 1311 this.removeItem(from, count); 1312 this.totalNodeCount -= arr.length; 1313 console.log(TEST_TAG + " after remove count:" + this.totalNodeCount); 1314 } 1315 1316 moveData(from: number, to: number): void { 1317 let tmp = this.data.splice(from, 1); 1318 this.data.splice(to, 0, tmp[0]); 1319 this.moveItem(from, to); 1320 } 1321 1322 onAttachToNode(target: FrameNode): void { 1323 console.log(TEST_TAG + " onAttachToNode id:" + target.getUniqueId()); 1324 this.hostNode = target; 1325 } 1326 1327 onDetachFromNode(): void { 1328 console.log(TEST_TAG + " onDetachFromNode"); 1329 } 1330 1331 onGetChildId(index: number): number { 1332 console.log(TEST_TAG + " onGetChildId:" + index); 1333 return index; 1334 } 1335 1336 onCreateChild(index: number): FrameNode { 1337 console.log(TEST_TAG + " onCreateChild:" + index); 1338 if (this.cachePool.length > 0) { 1339 let cacheNode = this.cachePool.pop(); 1340 if (cacheNode !== undefined) { 1341 console.log(TEST_TAG + " onCreateChild reused id:" + cacheNode.getUniqueId()); 1342 let text = cacheNode?.getFirstChild(); 1343 let textNode = text as typeNode.Text; 1344 textNode?.initialize(this.data[index]).fontSize(20); 1345 return cacheNode; 1346 } 1347 } 1348 console.log(TEST_TAG + " onCreateChild createNew"); 1349 let itemNode = typeNode.createNode(this.uiContext, "ListItem"); 1350 let textNode = typeNode.createNode(this.uiContext, "Text"); 1351 textNode.initialize(this.data[index]).fontSize(20); 1352 itemNode.appendChild(textNode); 1353 return itemNode; 1354 } 1355 1356 onDisposeChild(id: number, node: FrameNode): void { 1357 console.log(TEST_TAG + " onDisposeChild:" + id); 1358 if (this.cachePool.length < 10) { 1359 if (!this.cachePool.includes(node)) { 1360 console.log(TEST_TAG + " caching node id:" + node.getUniqueId()); 1361 this.cachePool.push(node); 1362 } 1363 } else { 1364 node.dispose(); 1365 } 1366 } 1367 1368 onUpdateChild(id: number, node: FrameNode): void { 1369 let index = id; 1370 let text = node.getFirstChild(); 1371 let textNode = text as typeNode.Text; 1372 textNode?.initialize(this.data[index]).fontSize(20); 1373 } 1374} 1375 1376class MyNodeAdapterController extends NodeController { 1377 rootNode: FrameNode | null = null; 1378 nodeAdapter: MyNodeAdapter | null = null; 1379 1380 makeNode(uiContext: UIContext): FrameNode | null { 1381 this.rootNode = new FrameNode(uiContext); 1382 let listNode = typeNode.createNode(uiContext, "List"); 1383 listNode.initialize({ space: 3 }).borderWidth(2).borderColor(Color.Black); 1384 this.rootNode.appendChild(listNode); 1385 this.nodeAdapter = new MyNodeAdapter(uiContext, 100); 1386 NodeAdapter.attachNodeAdapter(this.nodeAdapter, listNode); 1387 return this.rootNode; 1388 } 1389} 1390 1391@Entry 1392@Component 1393struct ListNodeTest { 1394 adapterController: MyNodeAdapterController = new MyNodeAdapterController(); 1395 1396 build() { 1397 Column() { 1398 Text("ListNode Adapter"); 1399 NodeContainer(this.adapterController) 1400 .width(300).height(300) 1401 .borderWidth(1).borderColor(Color.Black); 1402 Row() { 1403 Button("Reload") 1404 .onClick(() => { 1405 this.adapterController.nodeAdapter?.reloadData(50); 1406 }) 1407 Button("Change") 1408 .onClick(() => { 1409 this.adapterController.nodeAdapter?.changeData(5, 10) 1410 }) 1411 Button("Insert") 1412 .onClick(() => { 1413 this.adapterController.nodeAdapter?.insertData(10, 10); 1414 }) 1415 } 1416 1417 Row() { 1418 Button("Remove") 1419 .onClick(() => { 1420 this.adapterController.nodeAdapter?.removeData(10, 10); 1421 }) 1422 Button("Move") 1423 .onClick(() => { 1424 this.adapterController.nodeAdapter?.moveData(2, 5); 1425 }) 1426 Button("Refresh") 1427 .onClick(() => { 1428 this.adapterController.nodeAdapter?.refreshData(); 1429 }) 1430 Button("Detach") 1431 .onClick(() => { 1432 this.adapterController.nodeAdapter?.detachData(); 1433 }) 1434 } 1435 }.borderWidth(1) 1436 .width("100%") 1437 } 1438} 1439``` 1440