1# 自定义组件节点 (FrameNode) 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @xiang-shouxing--> 5<!--Designer: @xiang-shouxing--> 6<!--Tester: @sally__--> 7<!--Adviser: @HelloCrease--> 8 9## 概述 10 11对于拥有自定义前端的第三方框架(如JSON、XML、DOM树等),需将特定的DSL转换为ArkUI的声明式描述。如下图描述了JSON定义的前端框架和ArkUI声明式描述的对应关系。 12 13 14 15上述转换过程需要依赖额外的数据驱动,绑定至[Builder](../ui/state-management/arkts-builder.md)中,较为复杂且性能欠佳。这类框架通常依赖于ArkUI的布局、事件处理、基础的节点操作和自定义能力。大部分组件通过自定义实现,但需结合使用部分系统组件以实现混合显示,如下图示例既使用了FrameNode的自定义方法进行绘制,又使用了系统组件Column及其子组件Text,通过BuilderNode的方式将其挂载到根节点的FrameNode上混合显示。 16 17 18 19[FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md)的设计初衷正是为了解决上述转换问题。FrameNode表示组件树中的实体节点,与自定义占位容器组件[NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md)相配合,实现在占位容器内构建一棵自定义的节点树。该节点树支持动态操作,如节点的增加、修改和删除。基础的FrameNode具备设置通用属性和事件回调的功能,同时提供完整的自定义能力,涵盖自定义测量、布局和绘制等方面。 20 21除此之外,ArkUI还提供了获取和遍历系统组件对应代理FrameNode对象的能力(下文简称代理节点)。代理节点能够用于遍历整个UI的树形结构,支持获取系统组件节点的详细信息,以及额外注册组件的事件监听回调。 22 23## 创建和删除节点 24 25FrameNode提供了节点创建和删除的能力。可以通过FrameNode的构造函数创建自定义FrameNode节点,通过构造函数创建的节点对应一个实体的节点。同时,可以通过FrameNode中的[dispose](../reference/apis-arkui/js-apis-arkui-frameNode.md#dispose12)接口来实现与实体节点的绑定关系的解除。 26 27> **说明:** 28> 29> - 在创建FrameNode对象的时候需要传入必选参数UIContext,若未传入UIContext对象或者传入不合法,则节点创建抛出异常。 30> 31> - 自定义占位组件将节点进行显示的时候需要保证UI上下文一致,否则会出现显示异常。 32> 33> - 若不持有FrameNode对象,则该对象会在GC的时候被回收。 34 35## 判断节点是否可修改 36 37[isModifiable](../reference/apis-arkui/js-apis-arkui-frameNode.md#ismodifiable12)用于查询当前节点类型是否为系统组件的代理节点。当FrameNode节点作为系统组件的代理节点的时候,该节点不可修改。即无法修改代理节点的自身属性以及其子节点的结构。 38 39## 获取对应的RenderNode节点 40 41FrameNode提供了[getRenderNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#getrendernode)接口,用于获取FrameNode中的RenderNode。可以通过对获取到的RenderNode对象进行操作,动态修改FrameNode上绘制相关的属性,具体可修改的属性参考[RenderNode](arkts-user-defined-arktsNode-renderNode.md)的接口。 42 43> **说明:** 44> 45> - 无法获取系统组件代理FrameNode的RenderNode对象。 46> 47> - BuilderNode中调用[getFrameNode](../reference/apis-arkui/js-apis-arkui-builderNode.md#getframenode)获取得到的FrameNode节点对象中,可以通过getRenderNode获取对应的根节点的RenderNode对象。 48 49## 操作节点树 50 51FrameNode提供了节点的增、删、查、改的能力,能够修改非代理节点的子树结构。可以对所有FrameNode的节点的父子节点做出查询操作,并返回查询结果。 52 53> **说明:** 54> 55> 对节点进行增、删、改操作的时候,会对非法操作抛出异常信息。 56> 57> 通过查询获得的系统组件的代理节点,仅具备查询节点信息的作用,不具备修改节点属性的功能。代理节点不持有组件的实体节点,即不影响对应的节点的生命周期。 58> 59> 查询节点仅查询获得UI相关的节点,不返回语法节点。 60> 61> 使用自定义组件的场景下,可能查询获得自定义组件的新增节点,节点类型为“\_\_Common\_\_”。 62 63```ts 64import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI'; 65import { BusinessError } from '@kit.BasicServicesKit'; 66 67const TEST_TAG: string = "FrameNode" 68 69class Params { 70 text: string = "this is a text" 71} 72 73@Builder 74function buttonBuilder(params: Params) { 75 Column({ space: 10 }) { 76 Button(params.text) 77 .fontSize(12) 78 .borderRadius(8) 79 .borderWidth(2) 80 .backgroundColor(Color.Orange) 81 82 Button(params.text) 83 .fontSize(12) 84 .borderRadius(8) 85 .borderWidth(2) 86 .backgroundColor(Color.Pink) 87 } 88} 89 90class MyNodeController extends NodeController { 91 public buttonNode: BuilderNode<[Params]> | null = null; 92 public frameNode: FrameNode | null = null; 93 public childList: Array<FrameNode> = new Array<FrameNode>(); 94 public rootNode: FrameNode | null = null; 95 private uiContext: UIContext | null = null; 96 private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(buttonBuilder); 97 98 makeNode(uiContext: UIContext): FrameNode | null { 99 this.uiContext = uiContext; 100 if (this.rootNode == null) { 101 this.rootNode = new FrameNode(uiContext); 102 this.rootNode.commonAttribute 103 .width("50%") 104 .height(100) 105 .borderWidth(1) 106 .backgroundColor(Color.Gray) 107 } 108 109 if (this.frameNode == null) { 110 this.frameNode = new FrameNode(uiContext); 111 this.frameNode.commonAttribute 112 .width("100%") 113 .height(50) 114 .borderWidth(1) 115 .position({ x: 200, y: 0 }) 116 .backgroundColor(Color.Pink); 117 this.rootNode.appendChild(this.frameNode); 118 } 119 if (this.buttonNode == null) { 120 this.buttonNode = new BuilderNode<[Params]>(uiContext); 121 this.buttonNode.build(this.wrapBuilder, { text: "This is a Button" }) 122 this.rootNode.appendChild(this.buttonNode.getFrameNode()) 123 } 124 return this.rootNode; 125 } 126 127 operationFrameNodeWithFrameNode(frameNode: FrameNode | undefined | null) { 128 if (frameNode) { 129 console.info(TEST_TAG + " get ArkTSNode success.") 130 console.info(TEST_TAG + " check rootNode whether is modifiable " + frameNode.isModifiable()); 131 } 132 if (this.uiContext) { 133 let frameNode1 = new FrameNode(this.uiContext); 134 let frameNode2 = new FrameNode(this.uiContext); 135 frameNode1.commonAttribute.size({ width: 50, height: 50 }) 136 .backgroundColor(Color.Black) 137 .position({ x: 50, y: 60 }) 138 frameNode2.commonAttribute.size({ width: 50, height: 50 }) 139 .backgroundColor(Color.Orange) 140 .position({ x: 120, y: 60 }) 141 try { 142 frameNode?.appendChild(frameNode1); 143 console.info(TEST_TAG + " appendChild success "); 144 } catch (err) { 145 console.error(TEST_TAG + " appendChild fail :" + (err as BusinessError).code + " : " + 146 (err as BusinessError).message); 147 } 148 try { 149 frameNode?.insertChildAfter(frameNode2, null); 150 console.info(TEST_TAG + " insertChildAfter success "); 151 } catch (err) { 152 console.error(TEST_TAG + " insertChildAfter fail : " + (err as BusinessError).code + " : " + 153 (err as BusinessError).message); 154 } 155 setTimeout(() => { 156 try { 157 frameNode?.removeChild(frameNode?.getChild(0)) 158 console.info(TEST_TAG + " removeChild success "); 159 } catch (err) { 160 console.error(TEST_TAG + " removeChild fail : " + (err as BusinessError).code + " : " + 161 (err as BusinessError).message); 162 } 163 }, 2000) 164 setTimeout(() => { 165 try { 166 frameNode?.clearChildren(); 167 console.info(TEST_TAG + " clearChildren success "); 168 } catch (err) { 169 console.error(TEST_TAG + " clearChildren fail : " + (err as BusinessError).code + " : " + 170 (err as BusinessError).message); 171 } 172 }, 4000) 173 } 174 } 175 176 testInterfaceAboutSearch(frameNode: FrameNode | undefined | null): string { 177 let result: string = ""; 178 if (frameNode) { 179 result = result + `current node is ${frameNode.getNodeType()} \n`; 180 result = result + `parent node is ${frameNode.getParent()?.getNodeType()} \n`; 181 result = result + `child count is ${frameNode.getChildrenCount()} \n`; 182 result = result + `first child node is ${frameNode.getFirstChild()?.getNodeType()} \n`; 183 result = result + `second child node is ${frameNode.getChild(1)?.getNodeType()} \n`; 184 result = result + `previousSibling node is ${frameNode.getPreviousSibling()?.getNodeType()} \n`; 185 result = result + `nextSibling node is ${frameNode.getNextSibling()?.getNodeType()} \n`; 186 } 187 return result; 188 } 189 190 checkAppendChild(parent: FrameNode | undefined | null, child: FrameNode | undefined | null) { 191 try { 192 if (parent && child) { 193 parent.appendChild(child); 194 console.info(TEST_TAG + " appendChild success "); 195 } 196 } catch (err) { 197 console.error(TEST_TAG + " appendChild fail : " + (err as BusinessError).code + " : " + 198 (err as BusinessError).message); 199 } 200 } 201} 202 203@Entry 204@Component 205struct Index { 206 @State index: number = 0; 207 @State result: string = "" 208 private myNodeController: MyNodeController = new MyNodeController(); 209 210 build() { 211 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { 212 List({ space: 20, initialIndex: 0 }) { 213 ListItem() { 214 Column({ space: 5 }) { 215 Text("验证FrameNode子节点的增、删、改功能") 216 Button("对自定义FrameNode进行操作") 217 .fontSize(16) 218 .width(400) 219 .onClick(() => { 220 // 对FrameNode节点进行增、删、改操作,正常实现。 221 this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.frameNode); 222 }) 223 Button("对BuilderNode中的代理节点进行操作") 224 .fontSize(16) 225 .width(400) 226 .onClick(() => { 227 // 对BuilderNode代理节点进行增、删、改操作,捕获异常信息。 228 this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.buttonNode?.getFrameNode()); 229 }) 230 Button("对系统组件中的代理节点进行操作") 231 .fontSize(16) 232 .width(400) 233 .onClick(() => { 234 // 对代理节点进行增、删、改操作,捕获异常信息。 235 this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.rootNode?.getParent()); 236 }) 237 } 238 } 239 240 ListItem() { 241 Column({ space: 5 }) { 242 Text("验证FrameNode添加子节点的特殊场景") 243 Button("新增BuilderNode的代理节点") 244 .fontSize(16) 245 .width(400) 246 .onClick(() => { 247 let buttonNode = new BuilderNode<[Params]>(this.getUIContext()); 248 buttonNode.build(wrapBuilder<[Params]>(buttonBuilder), { text: "BUTTON" }) 249 this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, buttonNode?.getFrameNode()); 250 }) 251 Button("新增系统组件代理节点") 252 .fontSize(16) 253 .width(400) 254 .onClick(() => { 255 this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, 256 this.myNodeController?.rootNode?.getParent()); 257 }) 258 Button("新增已有父节点的自定义节点") 259 .fontSize(16) 260 .width(400) 261 .onClick(() => { 262 this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, 263 this.myNodeController?.rootNode); 264 }) 265 } 266 } 267 268 ListItem() { 269 Column({ space: 5 }) { 270 Text("验证FrameNode节点的查询功能") 271 Button("对自定义FrameNode进行操作") 272 .fontSize(16) 273 .width(400) 274 .onClick(() => { 275 // 对FrameNode节点进行进行查询。当前节点为NodeContainer的子节点。 276 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode); 277 setTimeout(() => { 278 // 对FrameNode节点进行进行查询。rootNode下的第一个子节点。 279 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.frameNode); 280 }, 2000) 281 }) 282 Button("对BuilderNode中的代理节点进行操作") 283 .fontSize(16) 284 .width(400) 285 .onClick(() => { 286 // 对BuilderNode代理节点进行进行查询。当前节点为BuilderNode中的Column节点。 287 this.result = 288 this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.buttonNode?.getFrameNode()); 289 }) 290 Button("对系统组件中的代理节点进行操作") 291 .fontSize(16) 292 .width(400) 293 .onClick(() => { 294 // 对代理节点进行查询。当前节点为NodeContainer。 295 this.result = 296 this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode?.getParent()); 297 }) 298 } 299 } 300 }.height("50%") 301 302 Text(`Result:\n${this.result}`) 303 .fontSize(16) 304 .width(400) 305 .height(200) 306 .padding(30) 307 .borderWidth(1) 308 Column() { 309 Text("This is a NodeContainer.") 310 .textAlign(TextAlign.Center) 311 .borderRadius(10) 312 .backgroundColor(0xFFFFFF) 313 .width('100%') 314 .fontSize(16) 315 NodeContainer(this.myNodeController) 316 .borderWidth(1) 317 .width(400) 318 .height(150) 319 } 320 } 321 .padding({ 322 left: 35, 323 right: 35, 324 top: 35, 325 bottom: 35 326 }) 327 .width("100%") 328 .height("100%") 329 } 330} 331``` 332 333## 使用moveTo移动命令式节点 334 335使用[moveTo](../reference/apis-arkui/js-apis-arkui-frameNode.md#moveto18)接口可以将FrameNode节点移动到新的父节点下,从而按需改变节点树结构。 336 337> **说明:** 338> 339> 当前FrameNode如果不可修改,抛出异常信息。 340> 341> 目标父节点为[typeNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#typenode12)时会校验子组件类型或个数,不满足抛出异常信息,限制情况请查看[typeNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#typenode12)描述。 342> 343> 当前不支持对无组件类型的命令式节点进行移动。 344> 345> 当前仅支持以下类型的[TypedFrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#typedframenode12)进行移动操作:[Stack](../reference/apis-arkui/js-apis-arkui-frameNode.md#stack12)、[XComponent](../reference/apis-arkui/js-apis-arkui-frameNode.md#xcomponent12)。对于其他类型的节点,移动操作不会生效。 346> 347> 当前仅支持根节点为以下类型组件的[BuilderNode](../reference/apis-arkui/js-apis-arkui-builderNode.md#buildernode-1)进行移动操作:[Stack](../reference/apis-arkui/arkui-ts/ts-container-stack.md)、[XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md)、[EmbeddedComponent](../reference/apis-arkui/arkui-ts/ts-container-embedded-component.md)。对于其他类型的组件,移动操作不会生效。 348 349```ts 350import { FrameNode, NodeController, UIContext, typeNode } from '@kit.ArkUI'; 351 352class MyNodeController extends NodeController { 353 uiContext: UIContext | null = null; 354 rootNode: FrameNode | null = null; 355 rowNode: FrameNode | null = null; 356 stackNode1: FrameNode | null = null; 357 stackNode2: FrameNode | null = null; 358 stackNode3: FrameNode | null = null; 359 360 makeNode(uiContext: UIContext): FrameNode | null { 361 this.uiContext = uiContext; 362 this.rootNode = new FrameNode(uiContext); 363 364 const row = typeNode.createNode(this.uiContext, 'Row'); 365 row.initialize({ space: 10 }); 366 this.rowNode = row; 367 this.rootNode.appendChild(this.rowNode); 368 369 const stack1 = typeNode.createNode(this.uiContext, 'Stack'); 370 stack1.commonAttribute.width(50).height(50).backgroundColor(Color.Pink); 371 this.stackNode1 = stack1; 372 this.rowNode?.appendChild(this.stackNode1); 373 const stack2 = typeNode.createNode(this.uiContext, 'Stack'); 374 stack2.commonAttribute.width(50).height(50).backgroundColor(Color.Yellow); 375 this.stackNode2 = stack2; 376 this.rowNode?.appendChild(this.stackNode2); 377 const stack3 = typeNode.createNode(this.uiContext, 'Stack'); 378 stack3.commonAttribute.width(50).height(50).backgroundColor(Color.Green); 379 this.stackNode3 = stack3; 380 this.rowNode?.appendChild(this.stackNode3); 381 382 return this.rootNode; 383 } 384} 385 386@Entry 387@Component 388struct Index { 389 private myNodeController1: MyNodeController = new MyNodeController() 390 private myNodeController2: MyNodeController = new MyNodeController() 391 392 build() { 393 Column({ space: 20 }) { 394 NodeContainer(this.myNodeController1) 395 NodeContainer(this.myNodeController2) 396 Button("move") 397 .onClick(() => { 398 this.myNodeController1.stackNode1?.moveTo(this.myNodeController2.rowNode, 2); 399 }) 400 } 401 .height('100%') 402 .width('100%') 403 } 404} 405``` 406 407 408 409## 设置节点通用属性和事件回调 410 411FrameNode提供了[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)。 412 413> **说明:** 414> 415> - 由于代理节点的属性不可修改,因此通过代理节点的commonAttribute修改节点的基础属性不生效。 416> 417> - 设置的基础事件与系统组件定义的事件平行,参与事件竞争。设置的基础事件不覆盖系统组件事件。同时设置两个事件回调的时候,优先回调系统组件事件。 418 419```ts 420import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI' 421 422class Params { 423 text: string = "this is a text" 424} 425 426@Builder 427function buttonBuilder(params: Params) { 428 Button(params.text) 429 .fontSize(12) 430 .borderRadius(8) 431 .borderWidth(2) 432 .backgroundColor(Color.Orange) 433 .onClick((event: ClickEvent) => { 434 console.info(`Button ${JSON.stringify(event)}`); 435 }) 436} 437 438class MyNodeController extends NodeController { 439 public buttonNode: BuilderNode<[Params]> | null = null; 440 public frameNode: FrameNode | null = null; 441 public rootNode: FrameNode | null = null; 442 private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(buttonBuilder); 443 444 makeNode(uiContext: UIContext): FrameNode | null { 445 if (this.rootNode == null) { 446 this.rootNode = new FrameNode(uiContext); 447 // 对rootNode进行属性修改,该节点为自定义的FrameNode节点,修改生效 448 this.rootNode.commonAttribute 449 .width("100%") 450 .height(100) 451 .borderWidth(1) 452 .backgroundColor(Color.Gray) 453 } 454 455 if (this.frameNode == null) { 456 this.frameNode = new FrameNode(uiContext); 457 // 对frameNode进行属性修改,该节点为自定义的FrameNode节点,修改生效 458 this.frameNode.commonAttribute 459 .width("50%") 460 .height(50) 461 .borderWidth(1) 462 .backgroundColor(Color.Pink); 463 this.rootNode.appendChild(this.frameNode); 464 } 465 if (this.buttonNode == null) { 466 this.buttonNode = new BuilderNode<[Params]>(uiContext); 467 this.buttonNode.build(this.wrapBuilder, { text: "This is a Button" }) 468 // 对BuilderNode中获取的FrameNode进行属性修改,该节点非自定义的FrameNode节点,修改不生效 469 this.buttonNode?.getFrameNode()?.commonAttribute.position({ x: 100, y: 100 }) 470 this.rootNode.appendChild(this.buttonNode.getFrameNode()) 471 } 472 return this.rootNode; 473 } 474 475 modifyNode(frameNode: FrameNode | null | undefined, sizeValue: SizeOptions, positionValue: Position) { 476 if (frameNode) { 477 frameNode.commonAttribute.size(sizeValue).position(positionValue); 478 } 479 } 480 481 addClickEvent(frameNode: FrameNode | null | undefined) { 482 if (frameNode) { 483 frameNode.commonEvent.setOnClick((event: ClickEvent) => { 484 console.info(`FrameNode ${JSON.stringify(event)}`); 485 }) 486 } 487 } 488} 489 490@Entry 491@Component 492struct Index { 493 private myNodeController: MyNodeController = new MyNodeController(); 494 495 build() { 496 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { 497 Column({ space: 10 }) { 498 Text("修改节点通用属性-宽高") 499 Button("modify ArkTS-FrameNode") 500 .onClick(() => { 501 // 获取到的是当前页面中的开发者创建的FrameNode对象,该节点可修改。即节点大小与位置。 502 console.info("Check the weather the node can be modified " + this.myNodeController?.frameNode 503 ?.isModifiable()); 504 this.myNodeController.modifyNode(this.myNodeController?.frameNode, { width: 150, height: 100 }, { 505 x: 100, 506 y: 0 507 }) 508 }) 509 Button("modify FrameNode get by BuilderNode") 510 .onClick(() => { 511 // 获取到的是当前页面中的BuilderNode的根节点,该节点不可修改。即节点大小与位置未发生改变。 512 console.info("Check the weather the node can be modified " + 513 this.myNodeController?.buttonNode?.getFrameNode() 514 ?.isModifiable()); 515 this.myNodeController.modifyNode(this.myNodeController?.buttonNode?.getFrameNode(), { 516 width: 100, 517 height: 100 518 }, { x: 50, y: 50 }) 519 }) 520 Button("modify proxyFrameNode get by search") 521 .onClick(() => { 522 // rootNode调用getParent()获取到的是当前页面中的NodeContainer节点,该节点不可修改。即节点大小与位置未发生改变。 523 console.info("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent() 524 ?.isModifiable()); 525 this.myNodeController.modifyNode(this.myNodeController?.rootNode?.getParent(), { 526 width: 500, 527 height: 500 528 }, { 529 x: 0, 530 y: 0 531 }) 532 }) 533 } 534 .padding({ 535 left: 35, 536 right: 35, 537 top: 35, 538 bottom: 35 539 }) 540 541 Column({ space: 10 }) { 542 Text("修改节点点击事件") 543 Button("add click event to ArkTS-FrameNode") 544 .onClick(() => { 545 // 获取到的是当前页面中的开发者创建的FrameNode对象,该节点可增加点击事件。 546 // 增加的点击事件参与事件竞争,即点击事件会在该节点被消费且不不再向父组件冒泡。 547 console.info("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent() 548 ?.isModifiable()); 549 this.myNodeController.addClickEvent(this.myNodeController?.frameNode) 550 }) 551 Button("add click event to FrameNode get by BuilderNode") 552 .onClick(() => { 553 // 获取到的是当前页面中的BuilderNode的根节点,该类节点可增加点击事件。 554 // 点击的时候优先回调通过系统组件接口设置的click事件回调,然后回调通过commonEvent增加的click监听。 555 console.info("Check the weather the node can be modified " + 556 this.myNodeController?.buttonNode?.getFrameNode() 557 ?.isModifiable()); 558 this.myNodeController.addClickEvent(this.myNodeController?.buttonNode?.getFrameNode()) 559 }) 560 Button("add click event to proxyFrameNode get by search") 561 .onClick(() => { 562 // rootNode调用getParent()获取到的是当前页面中的NodeContainer节点,该类节点可增加点击事件。 563 console.info("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent() 564 ?.isModifiable()); 565 this.myNodeController.addClickEvent(this.myNodeController?.rootNode?.getParent()); 566 }) 567 } 568 .padding({ 569 left: 35, 570 right: 35, 571 top: 35, 572 bottom: 35 573 }) 574 575 NodeContainer(this.myNodeController) 576 .borderWidth(1) 577 .width("100%") 578 .height(100) 579 .onClick((event: ClickEvent) => { 580 console.info(`NodeContainer ${JSON.stringify(event)}`); 581 }) 582 } 583 .padding({ 584 left: 35, 585 right: 35, 586 top: 35, 587 bottom: 35 588 }) 589 .width("100%") 590 .height("100%") 591 } 592} 593``` 594 595## 自定义测量布局与绘制 596 597通过重写[onDraw](../reference/apis-arkui/js-apis-arkui-frameNode.md#ondraw12)方法,可以自定义FrameNode的绘制内容。[invalidate](../reference/apis-arkui/js-apis-arkui-frameNode.md#invalidate12)接口可以主动触发节点的重新绘制。 598 599通过重写[onMeasure](../reference/apis-arkui/js-apis-arkui-frameNode.md#onmeasure12)可以自定义FrameNode的测量方式,使用[measure](../reference/apis-arkui/js-apis-arkui-frameNode.md#measure12)可以主动传递布局约束触发重新测量。 600 601通过重写[onLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#onlayout12)方法可以自定义FrameNode的布局方式,使用[layout](../reference/apis-arkui/js-apis-arkui-frameNode.md#layout12)方法可以主动传递位置信息并触发重新布局。 602 603[setNeedsLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#setneedslayout12)可以将当前节点标记,在下一帧触发重新布局。 604 605> **说明:** 606> 607> - 对节点进行dispose解引用后,由于FrameNode对象不再对应一个实体节点,invalidate无法触发原有绑定节点的刷新。 608> 609> - 通过onDraw方法进行的自定义绘制,绘制内容大小无法超出组件大小。 610 611```ts 612import { DrawContext, FrameNode, NodeController, Position, Size, UIContext, LayoutConstraint } from '@kit.ArkUI'; 613import { drawing } from '@kit.ArkGraphics2D'; 614 615function GetChildLayoutConstraint(constraint: LayoutConstraint, child: FrameNode): LayoutConstraint { 616 const size = child.getUserConfigSize(); 617 const width = Math.max( 618 Math.min(constraint.maxSize.width, size.width.value), 619 constraint.minSize.width 620 ); 621 const height = Math.max( 622 Math.min(constraint.maxSize.height, size.height.value), 623 constraint.minSize.height 624 ); 625 const finalSize: Size = { width, height }; 626 const res: LayoutConstraint = { 627 maxSize: finalSize, 628 minSize: finalSize, 629 percentReference: finalSize 630 }; 631 632 return res; 633} 634 635class MyFrameNode extends FrameNode { 636 public width: number = 100; 637 public offsetY: number = 0; 638 private space: number = 1; 639 uiContext: UIContext; 640 641 constructor(uiContext: UIContext) { 642 super(uiContext); 643 this.uiContext = uiContext; 644 } 645 646 onMeasure(constraint: LayoutConstraint): void { 647 let sizeRes: Size = { width: this.uiContext.vp2px(100), height: this.uiContext.vp2px(100) }; 648 for (let i = 0; i < this.getChildrenCount(); i++) { 649 let child = this.getChild(i); 650 if (child) { 651 let childConstraint = GetChildLayoutConstraint(constraint, child); 652 child.measure(childConstraint); 653 let size = child.getMeasuredSize(); 654 sizeRes.height += size.height + this.space; 655 sizeRes.width = Math.max(sizeRes.width, size.width); 656 } 657 } 658 this.setMeasuredSize(sizeRes); 659 } 660 661 onLayout(position: Position): void { 662 for (let i = 0; i < this.getChildrenCount(); i++) { 663 let child = this.getChild(i); 664 if (child) { 665 child.layout({ 666 x: this.uiContext.vp2px(100), 667 y: this.uiContext.vp2px(this.offsetY) 668 }); 669 let layoutPosition = child.getLayoutPosition(); 670 console.info("child position:" + JSON.stringify(layoutPosition)); 671 } 672 } 673 this.setLayoutPosition(position); 674 } 675 676 onDraw(context: DrawContext) { 677 const canvas = context.canvas; 678 const pen = new drawing.Pen(); 679 pen.setStrokeWidth(15); 680 pen.setColor({ 681 alpha: 255, 682 red: 255, 683 green: 0, 684 blue: 0 685 }); 686 canvas.attachPen(pen); 687 canvas.drawRect({ 688 left: 50, 689 right: this.width + 50, 690 top: 50, 691 bottom: this.width + 50, 692 }); 693 canvas.detachPen(); 694 } 695 696 addWidth() { 697 this.width = (this.width + 10) % 50 + 100; 698 } 699} 700 701class MyNodeController extends NodeController { 702 public rootNode: MyFrameNode | null = null; 703 704 makeNode(context: UIContext): FrameNode | null { 705 this.rootNode = new MyFrameNode(context); 706 this.rootNode?.commonAttribute?.size({ width: 100, height: 100 }).backgroundColor(Color.Green); 707 let frameNode: FrameNode = new FrameNode(context); 708 this.rootNode.appendChild(frameNode); 709 frameNode.commonAttribute.width(10).height(10).backgroundColor(Color.Pink); 710 return this.rootNode; 711 } 712} 713 714@Entry 715@Component 716struct Index { 717 private nodeController: MyNodeController = new MyNodeController(); 718 719 build() { 720 Row() { 721 Column() { 722 NodeContainer(this.nodeController) 723 .width('100%') 724 .height(200) 725 .backgroundColor('#FFF0F0F0') 726 Button('Invalidate') 727 .margin(10) 728 .onClick(() => { 729 this.nodeController?.rootNode?.addWidth(); 730 this.nodeController?.rootNode?.invalidate(); 731 }) 732 Button('UpdateLayout') 733 .onClick(() => { 734 let node = this.nodeController.rootNode; 735 node!.offsetY = (node!.offsetY + 10) % 110; 736 this.nodeController?.rootNode?.setNeedsLayout(); 737 }) 738 } 739 .width('100%') 740 .height('100%') 741 } 742 .height('100%') 743 } 744} 745``` 746 747## 查找节点及获取基础信息 748 749FrameNode提供了查询接口用于返回实体节点的基础信息。具体返回的信息内容参考FrameNode中提供的接口。 750 751查找获得FrameNode的方式包括三种: 752 7531. 使用[getFrameNodeById](../reference/apis-arkui/arkts-apis-uicontext-uicontext.md#getframenodebyid12)获取。 754 7552. 使用[getFrameNodeByUniqueId](../reference/apis-arkui/arkts-apis-uicontext-uicontext.md#getframenodebyuniqueid12)获取。 756 7573. 通过[无感监听](../reference/apis-arkui/js-apis-arkui-observer.md)获取。 758 759> **说明:** 760> 761> 1、当前接口提供的可查询的信息包括: 762> 763> - 节点大小:[getMeasuredSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getmeasuredsize12),[getUserConfigSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigsize12) 764> 765> - 布局信息:[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) 766> 767> - 节点信息:[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) 768> 769> 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组件等。 770 771## 获取节点位置偏移信息 772 773FrameNode提供了查询节点相对窗口、父组件以及屏幕位置偏移的信息接口([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),[getGlobalPositionOnDisplay](../reference/apis-arkui/js-apis-arkui-frameNode.md#getglobalpositionondisplay20),[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))。 774 775[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)三个接口获取到的位置信息关系如下图所示: 776 777 778 779```ts 780import { NodeController, FrameNode, UIContext } from '@kit.ArkUI'; 781 782const TEST_TAG: string = "FrameNode" 783 784class MyNodeController extends NodeController { 785 public frameNode: FrameNode | null = null; 786 private rootNode: FrameNode | null = null; 787 788 makeNode(uiContext: UIContext): FrameNode | null { 789 this.rootNode = new FrameNode(uiContext); 790 this.frameNode = new FrameNode(uiContext); 791 this.rootNode.appendChild(this.frameNode); 792 return this.rootNode; 793 } 794 795 getPositionToWindow() { 796 let positionToWindow = this.rootNode?.getPositionToWindow(); // 获取FrameNode相对于窗口的位置偏移 797 console.info(`${TEST_TAG} ${JSON.stringify(positionToWindow)}`); 798 } 799 800 getPositionToParent() { 801 let positionToParent = this.rootNode?.getPositionToParent(); // 获取FrameNode相对于父组件的位置偏移 802 console.info(`${TEST_TAG} ${JSON.stringify(positionToParent)}`); 803 } 804 805 getPositionToScreen() { 806 let positionToScreen = this.rootNode?.getPositionToScreen(); // 获取FrameNode相对于屏幕的位置偏移 807 console.info(`${TEST_TAG} ${JSON.stringify(positionToScreen)}`); 808 } 809 810 getGlobalPositionOnDisplay() { 811 let positionOnGlobalDisplay = this.rootNode?.getGlobalPositionOnDisplay(); // 获取FrameNode相对于全局屏幕的位置偏移 812 console.info(`${TEST_TAG} ${JSON.stringify(positionOnGlobalDisplay)}`); 813 } 814 815 getPositionToWindowWithTransform() { 816 let positionToWindowWithTransform = 817 this.rootNode?.getPositionToWindowWithTransform(); // 获取FrameNode相对于窗口带有绘制属性的位置偏移 818 console.info(`${TEST_TAG} ${JSON.stringify(positionToWindowWithTransform)}`); 819 } 820 821 getPositionToParentWithTransform() { 822 let positionToParentWithTransform = 823 this.rootNode?.getPositionToParentWithTransform(); // 获取FrameNode相对于父组件带有绘制属性的位置偏移 824 console.info(`${TEST_TAG} ${JSON.stringify(positionToParentWithTransform)}`); 825 } 826 827 getPositionToScreenWithTransform() { 828 let positionToScreenWithTransform = 829 this.rootNode?.getPositionToScreenWithTransform(); // 获取FrameNode相对于屏幕带有绘制属性的位置偏移 830 console.info(`${TEST_TAG} ${JSON.stringify(positionToScreenWithTransform)}`); 831 } 832} 833 834@Entry 835@Component 836struct Index { 837 private myNodeController: MyNodeController = new MyNodeController(); 838 839 build() { 840 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { 841 Button("getPositionToWindow") 842 .width(300) 843 .onClick(() => { 844 this.myNodeController.getPositionToWindow(); 845 }) 846 Button("getPositionToParent") 847 .width(300) 848 .onClick(() => { 849 this.myNodeController.getPositionToParent(); 850 }) 851 Button("getPositionToScreen") 852 .width(300) 853 .onClick(() => { 854 this.myNodeController.getPositionToScreen(); 855 }) 856 Button("getGlobalPositionOnDisplay") 857 .width(300) 858 .onClick(() => { 859 this.myNodeController.getGlobalPositionOnDisplay(); 860 }) 861 Button("getPositionToParentWithTransform") 862 .width(300) 863 .onClick(() => { 864 this.myNodeController.getPositionToParentWithTransform(); 865 }) 866 Button("getPositionToWindowWithTransform") 867 .width(300) 868 .onClick(() => { 869 this.myNodeController.getPositionToWindowWithTransform(); 870 }) 871 Button("getPositionToScreenWithTransform") 872 .width(300) 873 .onClick(() => { 874 this.myNodeController.getPositionToScreenWithTransform(); 875 }) 876 Column() { 877 Text("This is a NodeContainer.") 878 .textAlign(TextAlign.Center) 879 .borderRadius(10) 880 .backgroundColor(0xFFFFFF) 881 .width('100%') 882 .fontSize(16) 883 NodeContainer(this.myNodeController) 884 .borderWidth(1) 885 .width(300) 886 .height(100) 887 } 888 } 889 .padding({ 890 left: 35, 891 right: 35, 892 top: 35, 893 bottom: 35 894 }) 895 .width("100%") 896 .height("100%") 897 } 898} 899``` 900 901## 通过typeNode创建具体类型的FrameNode节点 902 903通过TypeNode创建具体类型的FrameNode节点,可以根据属性获取接口来检索用户设置的属性信息。 904 905```ts 906import { NodeController, FrameNode, UIContext, BuilderNode, typeNode } from '@kit.ArkUI'; 907 908class Params { 909 text: string = ""; 910 911 constructor(text: string) { 912 this.text = text; 913 } 914} 915 916@Builder 917function buildText(params: Params) { 918 Column() { 919 Text(params.text) 920 .id("buildText") 921 .border({ width: 1 }) 922 .padding(1) 923 .fontSize(25) 924 .fontWeight(FontWeight.Bold) 925 .margin({ top: 10 }) 926 .visibility(Visibility.Visible) 927 .opacity(0.7) 928 .customProperty("key1", "value1") 929 .width(300) 930 } 931} 932 933const TEST_TAG: string = "FrameNode" 934 935class MyNodeController extends NodeController { 936 public frameNode: typeNode.Column | null = null; 937 public uiContext: UIContext | undefined = undefined; 938 private rootNode: FrameNode | null = null; 939 private textNode: BuilderNode<[Params]> | null = null; 940 public textTypeNode: typeNode.Text | null = null; 941 private message: string = "DEFAULT"; 942 943 makeNode(uiContext: UIContext): FrameNode | null { 944 this.rootNode = new FrameNode(uiContext); 945 this.uiContext = uiContext; 946 this.frameNode = typeNode.createNode(uiContext, "Column"); 947 this.frameNode.attribute 948 .width("100%") 949 .height("100%") 950 this.rootNode.appendChild(this.frameNode); 951 this.textNode = new BuilderNode(uiContext); 952 this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message)); 953 this.frameNode.appendChild(this.textNode.getFrameNode()); 954 this.textTypeNode = typeNode.createNode(uiContext, "Text"); 955 this.textTypeNode.initialize("textTypeNode") 956 .fontSize(25) 957 .visibility(Visibility.Visible) 958 .id("textTypeNode") 959 this.frameNode.appendChild(this.textTypeNode); 960 return this.rootNode; 961 } 962 963 removeChild(frameNode: FrameNode) { 964 let parent = frameNode.getParent(); 965 if (parent) { 966 parent.removeChild(frameNode); 967 968 } 969 } 970 971 getUserConfigBorderWidth(frameNode: FrameNode) { 972 let userConfigBorderWidth = frameNode?.getUserConfigBorderWidth(); // 获取用户设置的边框宽度 973 console.info(`${TEST_TAG} ${JSON.stringify(userConfigBorderWidth)}`); 974 } 975 976 getUserConfigPadding(frameNode: FrameNode) { 977 let userConfigPadding = frameNode?.getUserConfigPadding(); // 获取用户设置的内边距 978 console.info(`${TEST_TAG} ${JSON.stringify(userConfigPadding)}`); 979 } 980 981 getUserConfigMargin(frameNode: FrameNode) { 982 let userConfigMargin = frameNode?.getUserConfigMargin(); // 获取用户设置的外边距 983 console.info(`${TEST_TAG} ${JSON.stringify(userConfigMargin)}`); 984 } 985 986 getUserConfigSize(frameNode: FrameNode) { 987 let userConfigSize = frameNode?.getUserConfigSize(); // 获取用户设置的宽高 988 console.info(`${TEST_TAG} ${JSON.stringify(userConfigSize)}`); 989 } 990 991 getId(frameNode: FrameNode) { 992 let id = frameNode?.getId(); // 获取用户设置的节点ID 993 console.info(`${TEST_TAG} ${id}`); 994 } 995 996 getUniqueId(frameNode: FrameNode) { 997 let uniqueId = frameNode?.getUniqueId(); // 获取系统分配的唯一标识的节点UniqueID 998 console.info(`${TEST_TAG} ${uniqueId}`); 999 } 1000 1001 getNodeType(frameNode: FrameNode) { 1002 let nodeType = frameNode?.getNodeType(); // 获取节点的类型 1003 console.info(`${TEST_TAG} ${nodeType}`); 1004 } 1005 1006 getOpacity(frameNode: FrameNode) { 1007 let opacity = frameNode?.getOpacity(); // 获取节点的不透明度 1008 console.info(`${TEST_TAG} ${JSON.stringify(opacity)}`); 1009 } 1010 1011 isVisible(frameNode: FrameNode) { 1012 let visible = frameNode?.isVisible(); // 获取节点是否可见 1013 console.info(`${TEST_TAG} ${JSON.stringify(visible)}`); 1014 } 1015 1016 isClipToFrame(frameNode: FrameNode) { 1017 let clipToFrame = frameNode?.isClipToFrame(); // 获取节点是否是剪裁到组件区域 1018 console.info(`${TEST_TAG} ${JSON.stringify(clipToFrame)}`); 1019 } 1020 1021 isAttached(frameNode: FrameNode) { 1022 let attached = frameNode?.isAttached(); // 获取节点是否被挂载到主节点树上 1023 console.info(`${TEST_TAG} ${JSON.stringify(attached)}`); 1024 } 1025 1026 getInspectorInfo(frameNode: FrameNode) { 1027 let inspectorInfo = frameNode?.getInspectorInfo(); // 获取节点的结构信息 1028 console.info(`${TEST_TAG} ${JSON.stringify(inspectorInfo)}`); 1029 } 1030} 1031 1032@Entry 1033@Component 1034struct Index { 1035 private myNodeController: MyNodeController = new MyNodeController(); 1036 @State index: number = 0; 1037 1038 build() { 1039 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { 1040 Column() { 1041 Text("This is a NodeContainer.") 1042 .textAlign(TextAlign.Center) 1043 .borderRadius(10) 1044 .backgroundColor(0xFFFFFF) 1045 .width('100%') 1046 .fontSize(16) 1047 NodeContainer(this.myNodeController) 1048 .borderWidth(1) 1049 .width(300) 1050 .height(100) 1051 } 1052 1053 Button("getUserConfigBorderWidth") 1054 .width(300) 1055 .onClick(() => { 1056 const uiContext: UIContext = this.getUIContext(); 1057 if (uiContext) { 1058 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1059 if (node) { 1060 this.myNodeController.getUserConfigBorderWidth(node); 1061 } 1062 } 1063 }) 1064 Button("getUserConfigPadding") 1065 .width(300) 1066 .onClick(() => { 1067 const uiContext: UIContext = this.getUIContext(); 1068 if (uiContext) { 1069 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1070 if (node) { 1071 this.myNodeController.getUserConfigPadding(node); 1072 } 1073 } 1074 }) 1075 Button("getUserConfigMargin") 1076 .width(300) 1077 .onClick(() => { 1078 const uiContext: UIContext = this.getUIContext(); 1079 if (uiContext) { 1080 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1081 if (node) { 1082 this.myNodeController.getUserConfigMargin(node); 1083 } 1084 } 1085 }) 1086 Button("getUserConfigSize") 1087 .width(300) 1088 .onClick(() => { 1089 const uiContext: UIContext = this.getUIContext(); 1090 if (uiContext) { 1091 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1092 if (node) { 1093 this.myNodeController.getUserConfigSize(node); 1094 } 1095 } 1096 }) 1097 Button("getId") 1098 .width(300) 1099 .onClick(() => { 1100 const uiContext: UIContext = this.getUIContext(); 1101 if (uiContext) { 1102 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1103 if (node) { 1104 this.myNodeController.getId(node); 1105 } 1106 } 1107 }) 1108 Button("getUniqueId") 1109 .width(300) 1110 .onClick(() => { 1111 const uiContext: UIContext = this.getUIContext(); 1112 if (uiContext) { 1113 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1114 if (node) { 1115 this.myNodeController.getUniqueId(node); 1116 } 1117 } 1118 }) 1119 Button("getNodeType") 1120 .width(300) 1121 .onClick(() => { 1122 const uiContext: UIContext = this.getUIContext(); 1123 if (uiContext) { 1124 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1125 if (node) { 1126 this.myNodeController.getNodeType(node); 1127 } 1128 } 1129 }) 1130 Button("getOpacity") 1131 .width(300) 1132 .onClick(() => { 1133 const uiContext: UIContext = this.getUIContext(); 1134 if (uiContext) { 1135 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1136 if (node) { 1137 this.myNodeController.getOpacity(node); 1138 } 1139 } 1140 }) 1141 Button("isVisible") 1142 .width(300) 1143 .onClick(() => { 1144 const uiContext: UIContext = this.getUIContext(); 1145 if (uiContext) { 1146 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1147 if (node) { 1148 this.myNodeController.isVisible(node); 1149 } 1150 } 1151 }) 1152 Button("isClipToFrame") 1153 .width(300) 1154 .onClick(() => { 1155 const uiContext: UIContext = this.getUIContext(); 1156 if (uiContext) { 1157 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1158 if (node) { 1159 this.myNodeController.isClipToFrame(node); 1160 } 1161 } 1162 }) 1163 Button("isAttached") 1164 .width(300) 1165 .onClick(() => { 1166 const uiContext: UIContext = this.getUIContext(); 1167 if (uiContext) { 1168 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1169 if (node) { 1170 this.myNodeController.isAttached(node); 1171 } 1172 } 1173 }) 1174 Button("remove Text") 1175 .width(300) 1176 .onClick(() => { 1177 const uiContext: UIContext = this.getUIContext(); 1178 if (uiContext) { 1179 const node: FrameNode | null = uiContext.getFrameNodeById("textTypeNode") || null; 1180 if (node) { 1181 this.myNodeController.removeChild(node); 1182 this.myNodeController.isAttached(node); 1183 } 1184 } 1185 }) 1186 Button("getInspectorInfo") 1187 .width(300) 1188 .onClick(() => { 1189 const uiContext: UIContext = this.getUIContext(); 1190 if (uiContext) { 1191 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1192 if (node) { 1193 this.myNodeController.getInspectorInfo(node); 1194 } 1195 } 1196 }) 1197 Button("getCustomProperty") 1198 .width(300) 1199 .onClick(() => { 1200 const uiContext: UIContext = this.getUIContext(); 1201 if (uiContext) { 1202 const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; 1203 if (node) { 1204 const property = node.getCustomProperty("key1"); 1205 console.info(TEST_TAG, JSON.stringify(property)); 1206 } 1207 } 1208 }) 1209 } 1210 .padding({ 1211 left: 35, 1212 right: 35, 1213 top: 35, 1214 bottom: 35 1215 }) 1216 .width("100%") 1217 .height("100%") 1218 } 1219} 1220``` 1221 1222## 解除当前FrameNode对象对实体FrameNode节点的引用关系 1223 1224使用[dispose](../reference/apis-arkui/js-apis-arkui-frameNode.md#dispose12)接口可以立即解除当前FrameNode对象对实体FrameNode节点的引用关系。 1225 1226> **说明:** 1227> 1228> 在调用dispose方法后,FrameNode对象不再对应任何实际的FrameNode节点。此时,若尝试调用以下查询接口:getMeasuredSize、getLayoutPosition、getUserConfigBorderWidth、getUserConfigPadding、getUserConfigMargin、getUserConfigSize,将导致应用程序触发jscrash。 1229> 1230> 通过[getUniqueId](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuniqueid12)可以判断当前FrameNode是否对应一个实体FrameNode节点。当UniqueId大于0时表示该对象对应一个实体FrameNode节点。 1231 1232```ts 1233import { NodeController, FrameNode, BuilderNode } from '@kit.ArkUI'; 1234 1235const TEST_TAG: string = "FrameNode"; 1236 1237@Component 1238struct TestComponent { 1239 build() { 1240 Column() { 1241 Text('This is a BuilderNode.') 1242 .fontSize(16) 1243 .fontWeight(FontWeight.Bold) 1244 } 1245 .width('100%') 1246 .backgroundColor(Color.Gray) 1247 } 1248 1249 aboutToAppear() { 1250 console.error(TEST_TAG + ' aboutToAppear'); 1251 } 1252 1253 aboutToDisappear() { 1254 console.error(TEST_TAG + ' aboutToDisappear'); 1255 } 1256} 1257 1258@Builder 1259function buildComponent() { 1260 TestComponent() 1261} 1262 1263class MyNodeController extends NodeController { 1264 private rootNode: FrameNode | null = null; 1265 private builderNode: BuilderNode<[]> | null = null; 1266 1267 makeNode(uiContext: UIContext): FrameNode | null { 1268 this.rootNode = new FrameNode(uiContext); 1269 this.builderNode = new BuilderNode(uiContext, { selfIdealSize: { width: 200, height: 100 } }); 1270 this.builderNode.build(new WrappedBuilder(buildComponent)); 1271 1272 const rootRenderNode = this.rootNode.getRenderNode(); 1273 if (rootRenderNode !== null) { 1274 rootRenderNode.size = { width: 200, height: 200 }; 1275 rootRenderNode.backgroundColor = 0xff00ff00; 1276 rootRenderNode.appendChild(this.builderNode!.getFrameNode()!.getRenderNode()); 1277 } 1278 1279 return this.rootNode; 1280 } 1281 1282 printUniqueId(): void { 1283 if (this.rootNode !== null && this.builderNode !== null) { 1284 console.info(`${TEST_TAG} rootNode's uniqueId: ${this.rootNode.getUniqueId()}`); 1285 const frameNode = this.builderNode.getFrameNode(); 1286 if (frameNode) { 1287 console.info(`${TEST_TAG} the uniqueId of builderNode's framenode: ${frameNode.getUniqueId()}`); 1288 } else { 1289 console.info(`${TEST_TAG} builderNode's framenode is undefined`); 1290 } 1291 } 1292 } 1293 1294 disposeFrameNode(): void { 1295 if (this.rootNode !== null && this.builderNode !== null) { 1296 console.info(`${TEST_TAG} disposeFrameNode`); 1297 this.rootNode.removeChild(this.builderNode.getFrameNode()); 1298 this.builderNode.dispose(); 1299 1300 this.rootNode.dispose(); 1301 } 1302 } 1303 1304 removeBuilderNode(): void { 1305 const rootRenderNode = this.rootNode!.getRenderNode(); 1306 if (rootRenderNode !== null && this.builderNode !== null && this.builderNode.getFrameNode() !== null) { 1307 rootRenderNode.removeChild(this.builderNode!.getFrameNode()!.getRenderNode()); 1308 } 1309 } 1310} 1311 1312@Entry 1313@Component 1314struct Index { 1315 private myNodeController: MyNodeController = new MyNodeController(); 1316 1317 build() { 1318 Column({ space: 4 }) { 1319 NodeContainer(this.myNodeController) 1320 Button('FrameNode dispose') 1321 .onClick(() => { 1322 this.myNodeController.printUniqueId(); 1323 this.myNodeController.disposeFrameNode(); 1324 this.myNodeController.printUniqueId(); 1325 }) 1326 .width('100%') 1327 } 1328 } 1329} 1330``` 1331 1332## 查询当前FrameNode是否解除引用 1333 1334前端节点均绑定有相应的后端实体节点,当节点调用dispose接口解除绑定后,再次调用接口可能会出现crash、返回默认值的情况。 1335 1336从API version 20开始,使用[isDisposed](../reference/apis-arkui/js-apis-arkui-frameNode.md#isdisposed20)接口查询当前FrameNode对象是否已解除与后端实体节点的引用关系,从而可以在操作节点前检查其有效性,避免潜在风险。 1337 1338```ts 1339import { NodeController, FrameNode } from '@kit.ArkUI'; 1340 1341class MyNodeController extends NodeController { 1342 private rootNode: FrameNode | null = null; 1343 1344 makeNode(uiContext: UIContext): FrameNode | null { 1345 this.rootNode = new FrameNode(uiContext); 1346 this.rootNode.commonAttribute.width(100).height(100).backgroundColor(Color.Pink); 1347 1348 return this.rootNode; 1349 } 1350 1351 disposeFrameNode() { 1352 // 解除当前FrameNode对象对实体FrameNode节点的引用关系 1353 this.rootNode?.dispose(); 1354 } 1355 1356 isDisposed() : string { 1357 if (this.rootNode !== null) { 1358 // 查询FrameNode是否解除引用 1359 if (this.rootNode.isDisposed()) { 1360 return 'frameNode isDisposed is true'; 1361 } 1362 else { 1363 return 'frameNode isDisposed is false'; 1364 } 1365 } 1366 return 'frameNode is null'; 1367 } 1368} 1369 1370@Entry 1371@Component 1372struct Index { 1373 @State text: string = '' 1374 private myNodeController: MyNodeController = new MyNodeController(); 1375 1376 build() { 1377 Column({ space: 4 }) { 1378 NodeContainer(this.myNodeController) 1379 Button('FrameNode dispose') 1380 .onClick(() => { 1381 this.myNodeController.disposeFrameNode(); 1382 this.text = ''; 1383 }) 1384 .width(200) 1385 .height(50) 1386 Button('FrameNode isDisposed') 1387 .onClick(() => { 1388 this.text = this.myNodeController.isDisposed(); 1389 }) 1390 .width(200) 1391 .height(50) 1392 Text(this.text) 1393 .fontSize(25) 1394 } 1395 .width('100%') 1396 .height('100%') 1397 } 1398} 1399``` 1400 1401## FrameNode的数据懒加载能力 1402 1403提供[NodeAdapter](../reference/apis-arkui/js-apis-arkui-frameNode.md#nodeadapter12)对象替代ArkTS侧的LazyForEach功能,提供自定义节点的数据懒加载功能,实现按需迭代数据。 1404 1405> **说明:** 1406> 1407> 入参不能为负数,入参为负数时不做处理。 1408 1409```ts 1410import { FrameNode, NodeController, NodeAdapter, typeNode } from '@kit.ArkUI'; 1411 1412const TEST_TAG: string = "FrameNode"; 1413 1414class MyNodeAdapter extends NodeAdapter { 1415 uiContext: UIContext 1416 cachePool: Array<FrameNode> = new Array(); 1417 changed: boolean = false 1418 reloadTimes: number = 0; 1419 data: Array<string> = new Array(); 1420 hostNode?: FrameNode 1421 1422 constructor(uiContext: UIContext, count: number) { 1423 super(); 1424 this.uiContext = uiContext; 1425 this.totalNodeCount = count; 1426 this.loadData(); 1427 } 1428 1429 reloadData(count: number): void { 1430 this.reloadTimes++; 1431 NodeAdapter.attachNodeAdapter(this, this.hostNode); 1432 this.totalNodeCount = count; 1433 this.loadData(); 1434 this.reloadAllItems(); 1435 } 1436 1437 refreshData(): void { 1438 let items = this.getAllAvailableItems() 1439 console.info(TEST_TAG + " get All items:" + items.length); 1440 this.totalNodeCount -= 1; 1441 this.reloadAllItems(); 1442 } 1443 1444 detachData(): void { 1445 NodeAdapter.detachNodeAdapter(this.hostNode); 1446 this.reloadTimes = 0; 1447 } 1448 1449 loadData(): void { 1450 for (let i = 0; i < this.totalNodeCount; i++) { 1451 this.data[i] = "Adapter ListItem " + i + " r:" + this.reloadTimes; 1452 } 1453 } 1454 1455 changeData(from: number, count: number): void { 1456 this.changed = !this.changed; 1457 for (let i = 0; i < count; i++) { 1458 let index = i + from; 1459 this.data[index] = "Adapter ListItem " + (this.changed ? "changed:" : "") + index + " r:" + this.reloadTimes; 1460 } 1461 this.reloadItem(from, count); 1462 } 1463 1464 insertData(from: number, count: number): void { 1465 for (let i = 0; i < count; i++) { 1466 let index = i + from; 1467 this.data.splice(index, 0, "Adapter ListItem " + from + "-" + i); 1468 } 1469 this.insertItem(from, count); 1470 this.totalNodeCount += count; 1471 console.info(TEST_TAG + " after insert count:" + this.totalNodeCount); 1472 } 1473 1474 removeData(from: number, count: number): void { 1475 let arr = this.data.splice(from, count); 1476 this.removeItem(from, count); 1477 this.totalNodeCount -= arr.length; 1478 console.info(TEST_TAG + " after remove count:" + this.totalNodeCount); 1479 } 1480 1481 moveData(from: number, to: number): void { 1482 let tmp = this.data.splice(from, 1); 1483 this.data.splice(to, 0, tmp[0]); 1484 this.moveItem(from, to); 1485 } 1486 1487 onAttachToNode(target: FrameNode): void { 1488 console.info(TEST_TAG + " onAttachToNode id:" + target.getUniqueId()); 1489 this.hostNode = target; 1490 } 1491 1492 onDetachFromNode(): void { 1493 console.info(TEST_TAG + " onDetachFromNode"); 1494 } 1495 1496 onGetChildId(index: number): number { 1497 console.info(TEST_TAG + " onGetChildId:" + index); 1498 return index; 1499 } 1500 1501 onCreateChild(index: number): FrameNode { 1502 console.info(TEST_TAG + " onCreateChild:" + index); 1503 if (this.cachePool.length > 0) { 1504 let cacheNode = this.cachePool.pop(); 1505 if (cacheNode !== undefined) { 1506 console.info(TEST_TAG + " onCreateChild reused id:" + cacheNode.getUniqueId()); 1507 let text = cacheNode?.getFirstChild(); 1508 let textNode = text as typeNode.Text; 1509 textNode?.initialize(this.data[index]).fontSize(20); 1510 return cacheNode; 1511 } 1512 } 1513 console.info(TEST_TAG + " onCreateChild createNew"); 1514 let itemNode = typeNode.createNode(this.uiContext, "ListItem"); 1515 let textNode = typeNode.createNode(this.uiContext, "Text"); 1516 textNode.initialize(this.data[index]).fontSize(20); 1517 itemNode.appendChild(textNode); 1518 return itemNode; 1519 } 1520 1521 onDisposeChild(id: number, node: FrameNode): void { 1522 console.info(TEST_TAG + " onDisposeChild:" + id); 1523 if (this.cachePool.length < 10) { 1524 if (!this.cachePool.includes(node)) { 1525 console.info(TEST_TAG + " caching node id:" + node.getUniqueId()); 1526 this.cachePool.push(node); 1527 } 1528 } else { 1529 node.dispose(); 1530 } 1531 } 1532 1533 onUpdateChild(id: number, node: FrameNode): void { 1534 let index = id; 1535 let text = node.getFirstChild(); 1536 let textNode = text as typeNode.Text; 1537 textNode?.initialize(this.data[index]).fontSize(20); 1538 } 1539} 1540 1541class MyNodeAdapterController extends NodeController { 1542 rootNode: FrameNode | null = null; 1543 nodeAdapter: MyNodeAdapter | null = null; 1544 1545 makeNode(uiContext: UIContext): FrameNode | null { 1546 this.rootNode = new FrameNode(uiContext); 1547 let listNode = typeNode.createNode(uiContext, "List"); 1548 listNode.initialize({ space: 3 }).borderWidth(2).borderColor(Color.Black); 1549 this.rootNode.appendChild(listNode); 1550 this.nodeAdapter = new MyNodeAdapter(uiContext, 100); 1551 NodeAdapter.attachNodeAdapter(this.nodeAdapter, listNode); 1552 return this.rootNode; 1553 } 1554} 1555 1556@Entry 1557@Component 1558struct ListNodeTest { 1559 adapterController: MyNodeAdapterController = new MyNodeAdapterController(); 1560 1561 build() { 1562 Column() { 1563 Text("ListNode Adapter"); 1564 NodeContainer(this.adapterController) 1565 .width(300).height(300) 1566 .borderWidth(1).borderColor(Color.Black); 1567 Row() { 1568 Button("Reload") 1569 .onClick(() => { 1570 this.adapterController.nodeAdapter?.reloadData(50); 1571 }) 1572 Button("Change") 1573 .onClick(() => { 1574 this.adapterController.nodeAdapter?.changeData(5, 10) 1575 }) 1576 Button("Insert") 1577 .onClick(() => { 1578 this.adapterController.nodeAdapter?.insertData(10, 10); 1579 }) 1580 } 1581 1582 Row() { 1583 Button("Remove") 1584 .onClick(() => { 1585 this.adapterController.nodeAdapter?.removeData(10, 10); 1586 }) 1587 Button("Move") 1588 .onClick(() => { 1589 this.adapterController.nodeAdapter?.moveData(2, 5); 1590 }) 1591 Button("Refresh") 1592 .onClick(() => { 1593 this.adapterController.nodeAdapter?.refreshData(); 1594 }) 1595 Button("Detach") 1596 .onClick(() => { 1597 this.adapterController.nodeAdapter?.detachData(); 1598 }) 1599 } 1600 }.borderWidth(1) 1601 .width("100%") 1602 } 1603} 1604``` 1605 1606## 查询LazyForEach中的FrameNode节点信息 1607 1608如果FrameNode子节点中包含[LazyForEach](../reference/apis-arkui/arkui-ts/ts-rendering-control-lazyforeach.md)节点,[getChild](../reference/apis-arkui/js-apis-arkui-frameNode.md#getchild15)接口支持指定子节点展开模式[ExpandMode](../reference/apis-arkui/js-apis-arkui-frameNode.md#expandmode15),以不同展开模式获取子节点。 1609 1610当前支持如下子节点展开模式: 1611 1612- ExpandMode.NOT_EXPAND:表示不展开当前FrameNode的子节点。如果FrameNode子节点中包含LazyForEach节点,获取在主节点树上的子节点时,不展开当前FrameNode的子节点。子节点序列号按在主节点树上的子节点计算。 1613- ExpandMode.EXPAND:表示展开当前FrameNode的子节点。如果FrameNode子节点中包含LazyForEach节点,获取任何子节点时,展开当前FrameNode的子节点。子节点序列号按所有子节点计算。 1614- ExpandMode.LAZY_EXPAND:表示按需展开当前FrameNode的子节点。如果FrameNode子节点中包含LazyForEach节点,获取在主节点树上的子节点时,不展开当前FrameNode的子节点;获取不在主节点树上的子节点时,展开当前FrameNode的子节点。子节点序列号按所有子节点计算。 1615 1616可以使用[getFirstChildIndexWithoutExpand](../reference/apis-arkui/js-apis-arkui-frameNode.md#getfirstchildindexwithoutexpand15)和[getLastChildIndexWithoutExpand](../reference/apis-arkui/js-apis-arkui-frameNode.md#getlastchildindexwithoutexpand15)获取当前节点第一个和最后一个在主节点树上的子节点的序列号,其中子节点序列号按所有子节点计算。 1617 1618```ts 1619import { NodeController, FrameNode, UIContext, BuilderNode, ExpandMode, LengthUnit } from '@kit.ArkUI'; 1620 1621const TEST_TAG: string = "FrameNode "; 1622 1623// BasicDataSource实现了IDataSource接口,用于管理listener监听,以及通知LazyForEach数据更新 1624class BasicDataSource implements IDataSource { 1625 private listeners: DataChangeListener[] = []; 1626 private originDataArray: string[] = []; 1627 1628 public totalCount(): number { 1629 return 0; 1630 } 1631 1632 public getData(index: number): string { 1633 return this.originDataArray[index]; 1634 } 1635 1636 // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听 1637 registerDataChangeListener(listener: DataChangeListener): void { 1638 if (this.listeners.indexOf(listener) < 0) { 1639 console.info('add listener'); 1640 this.listeners.push(listener); 1641 } 1642 } 1643 1644 // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听 1645 unregisterDataChangeListener(listener: DataChangeListener): void { 1646 const pos = this.listeners.indexOf(listener); 1647 if (pos >= 0) { 1648 console.info('remove listener'); 1649 this.listeners.splice(pos, 1); 1650 } 1651 } 1652 1653 // 通知LazyForEach组件需要重载所有子组件 1654 notifyDataReload(): void { 1655 this.listeners.forEach(listener => { 1656 listener.onDataReloaded(); 1657 }) 1658 } 1659 1660 // 通知LazyForEach组件需要在index对应索引处添加子组件 1661 notifyDataAdd(index: number): void { 1662 this.listeners.forEach(listener => { 1663 listener.onDataAdd(index); 1664 // 写法2:listener.onDatasetChange([{type: DataOperationType.ADD, index: index}]); 1665 }) 1666 } 1667 1668 // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件 1669 notifyDataChange(index: number): void { 1670 this.listeners.forEach(listener => { 1671 listener.onDataChange(index); 1672 // 写法2:listener.onDatasetChange([{type: DataOperationType.CHANGE, index: index}]); 1673 }) 1674 } 1675 1676 // 通知LazyForEach组件需要在index对应索引处删除该子组件 1677 notifyDataDelete(index: number): void { 1678 this.listeners.forEach(listener => { 1679 listener.onDataDelete(index); 1680 // 写法2:listener.onDatasetChange([{type: DataOperationType.DELETE, index: index}]); 1681 }) 1682 } 1683 1684 // 通知LazyForEach组件将from索引和to索引处的子组件进行交换 1685 notifyDataMove(from: number, to: number): void { 1686 this.listeners.forEach(listener => { 1687 listener.onDataMove(from, to); 1688 // 写法2:listener.onDatasetChange( 1689 // [{type: DataOperationType.EXCHANGE, index: {start: from, end: to}}]); 1690 }) 1691 } 1692 1693 notifyDatasetChange(operations: DataOperation[]): void { 1694 this.listeners.forEach(listener => { 1695 listener.onDatasetChange(operations); 1696 }) 1697 } 1698} 1699 1700class MyDataSource extends BasicDataSource { 1701 private dataArray: string[] = [] 1702 1703 public totalCount(): number { 1704 return this.dataArray.length; 1705 } 1706 1707 public getData(index: number): string { 1708 return this.dataArray[index]; 1709 } 1710 1711 public addData(index: number, data: string): void { 1712 this.dataArray.splice(index, 0, data); 1713 this.notifyDataAdd(index); 1714 } 1715 1716 public pushData(data: string): void { 1717 this.dataArray.push(data); 1718 this.notifyDataAdd(this.dataArray.length - 1); 1719 } 1720} 1721 1722class Params { 1723 data: MyDataSource | null = null; 1724 scroller: Scroller | null = null; 1725 constructor(data: MyDataSource, scroller: Scroller) { 1726 this.data = data; 1727 this.scroller = scroller; 1728 } 1729} 1730 1731@Builder 1732function buildData(params: Params) { 1733 List({ scroller: params.scroller }) { 1734 LazyForEach(params.data, (item: string) => { 1735 ListItem() { 1736 Column() { 1737 Text(item) 1738 .fontSize(20) 1739 .onAppear(() => { 1740 console.info(TEST_TAG + " node appear: " + item) 1741 }) 1742 .backgroundColor(Color.Pink) 1743 .margin({ 1744 top: 30, 1745 bottom: 30, 1746 left: 10, 1747 right: 10 1748 }) 1749 } 1750 } 1751 .id(item) 1752 }, (item: string) => item) 1753 } 1754 .cachedCount(5) 1755 .listDirection(Axis.Horizontal) 1756} 1757 1758class MyNodeController extends NodeController { 1759 private rootNode: FrameNode | null = null; 1760 private uiContext: UIContext | null = null; 1761 private data: MyDataSource = new MyDataSource(); 1762 private scroller: Scroller = new Scroller(); 1763 1764 makeNode(uiContext: UIContext): FrameNode | null { 1765 this.uiContext = uiContext; 1766 for (let i = 0; i <= 20; i++) { 1767 this.data.pushData(`N${i}`); 1768 } 1769 const params: Params = new Params(this.data, this.scroller); 1770 const dataNode: BuilderNode<[Params]> = new BuilderNode(uiContext); 1771 dataNode.build(wrapBuilder<[Params]>(buildData), params); 1772 this.rootNode = dataNode.getFrameNode(); 1773 const scrollToIndexOptions: ScrollToIndexOptions = { 1774 extraOffset: { 1775 value: 20, unit: LengthUnit.VP 1776 } 1777 }; 1778 this.scroller.scrollToIndex(6, true, ScrollAlign.START, scrollToIndexOptions); 1779 return this.rootNode; 1780 } 1781 1782 getFirstChildIndexWithoutExpand() { 1783 console.info(`${TEST_TAG} getFirstChildIndexWithoutExpand: ${this.rootNode!.getFirstChildIndexWithoutExpand()}`); 1784 } 1785 1786 getLastChildIndexWithoutExpand() { 1787 console.info(`${TEST_TAG} getLastChildIndexWithoutExpand: ${this.rootNode!.getLastChildIndexWithoutExpand()}`); 1788 } 1789 1790 getChildWithNotExpand() { 1791 const childNode = this.rootNode!.getChild(3, ExpandMode.NOT_EXPAND); 1792 console.info(TEST_TAG + " getChild(3, ExpandMode.NOT_EXPAND): " + childNode!.getId()); 1793 if (childNode!.getId() === "N9") { 1794 console.info(TEST_TAG + " getChild(3, ExpandMode.NOT_EXPAND) result: success."); 1795 } else { 1796 console.info(TEST_TAG + " getChild(3, ExpandMode.NOT_EXPAND) result: fail."); 1797 } 1798 } 1799 1800 getChildWithExpand() { 1801 const childNode = this.rootNode!.getChild(3, ExpandMode.EXPAND); 1802 console.info(TEST_TAG + " getChild(3, ExpandMode.EXPAND): " + childNode!.getId()); 1803 if (childNode!.getId() === "N3") { 1804 console.info(TEST_TAG + " getChild(3, ExpandMode.EXPAND) result: success."); 1805 } else { 1806 console.info(TEST_TAG + " getChild(3, ExpandMode.EXPAND) result: fail."); 1807 } 1808 } 1809 1810 getChildWithLazyExpand() { 1811 const childNode = this.rootNode!.getChild(3, ExpandMode.LAZY_EXPAND); 1812 console.info(TEST_TAG + " getChild(3, ExpandMode.LAZY_EXPAND): " + childNode!.getId()); 1813 if (childNode!.getId() === "N3") { 1814 console.info(TEST_TAG + " getChild(3, ExpandMode.LAZY_EXPAND) result: success."); 1815 } else { 1816 console.info(TEST_TAG + " getChild(3, ExpandMode.LAZY_EXPAND) result: fail."); 1817 } 1818 } 1819} 1820 1821@Entry 1822@Component 1823struct Index { 1824 private myNodeController: MyNodeController = new MyNodeController(); 1825 private scroller: Scroller = new Scroller(); 1826 1827 build() { 1828 Scroll(this.scroller) { 1829 Column({ space: 8 }) { 1830 Column() { 1831 Text("This is a NodeContainer.") 1832 .textAlign(TextAlign.Center) 1833 .borderRadius(10) 1834 .backgroundColor(0xFFFFFF) 1835 .width('100%') 1836 .fontSize(16) 1837 NodeContainer(this.myNodeController) 1838 .borderWidth(1) 1839 .width(300) 1840 .height(100) 1841 } 1842 1843 Button("getFirstChildIndexWithoutExpand") 1844 .width(300) 1845 .onClick(() => { 1846 this.myNodeController.getFirstChildIndexWithoutExpand(); 1847 }) 1848 Button("getLastChildIndexWithoutExpand") 1849 .width(300) 1850 .onClick(() => { 1851 this.myNodeController.getLastChildIndexWithoutExpand(); 1852 }) 1853 Button("getChildWithNotExpand") 1854 .width(300) 1855 .onClick(() => { 1856 this.myNodeController.getChildWithNotExpand(); 1857 }) 1858 Button("getChildWithExpand") 1859 .width(300) 1860 .onClick(() => { 1861 this.myNodeController.getChildWithExpand(); 1862 }) 1863 Button("getChildWithLazyExpand") 1864 .width(300) 1865 .onClick(() => { 1866 this.myNodeController.getChildWithLazyExpand(); 1867 }) 1868 } 1869 .width("100%") 1870 } 1871 .scrollable(ScrollDirection.Vertical) // 滚动方向纵向 1872 } 1873} 1874```