1# FrameNode 2 3## 概述 4 5对于具备自己前端定义的三方框架,需要将特定的dsl转换成为ArkUI的声明式描述。这个转换过程需依赖额外的数据驱动绑定至[Builder](../quick-start/arkts-builder.md)中,转换比较复杂且性能较低。这一类框架一般依赖系统ArkUI框架的布局、事件能力,以及最基础的节点操作和自定义能力,大部分组件通过自定义完成,但是需要使用部分原生组件混合显示。[FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md)的设计就是为了解决上述的问题。 6 7FrameNode表示组件树的实体节点,配合自定义占位容器组件[NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md)等,在占位容器内挂载一棵自定义的节点树,并对这个节点树中的节点进行动态的增加、修改、删除等操作。基础的FrameNode可以设置通用属性、设置事件回调,并提供完整的自定义能力,包括自定义测量、布局以及绘制。 8 9除此之外,ArkUI框架还提供获取和遍历获得原生组件对应的代理FrameNode对象的能力,下文简称代理节点。代理节点可以用于需要遍历整个UI的树形结构,并支持获取原生组件节点的具体信息或者额外注册组件的事件监听回调。 10 11## 创建和删除节点 12 13FrameNode提供了节点创建和删除的能力。可以通过FrameNode的构造函数创建自定义FrameNode节点,通过构造函数创建的节点对应一个实体的节点。同时,可以通过FrameNode中的[dispose](../reference/apis-arkui/js-apis-arkui-frameNode.md#dispose12)接口来实现与实体节点的绑定关系的解除。 14 15> **说明:** 16> 17> - 在创建FrameNode对象的时候需要传入必选参数UIContext,若未传入UIContext对象或者传入不合法,则节点创建抛出异常。 18> 19> - 自定义占位组件将节点进行显示的时候需要保证UI上下文一致,否则会出现显示异常。 20> 21> - 若不持有FrameNode对象,则该对象会在GC的时候被回收。 22 23## 判断节点是否可修改 24 25[isModifiable](../reference/apis-arkui/js-apis-arkui-frameNode.md#ismodifiable12)用于查询当前节点类型是否为原生组件的代理节点。当FrameNode节点作为原生组件的代理节点的时候,该节点不可修改。即无法修改代理节点的自身属性以及其子节点的结构。 26 27## 获取对应的RenderNode节点 28 29FrameNode提供了[getRenderNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#getrendernode)接口,用于获取FrameNode中的RenderNode。可以通过对获取到的RenderNode对象进行操作,动态修改FrameNode上绘制相关的属性,具体可修改的属性参考RenderNode的接口。 30 31> **说明:** 32> 33> - 无法获取原生组件代理FrameNode的RenderNode对象。 34> 35> - BuilderNode中调用[getFrameNode](../reference/apis-arkui/js-apis-arkui-builderNode.md#getframenode)获取得到的FrameNode节点对象中,可以通过getRenderNode获取对应的根节点的RenderNode对象。 36 37## 操作节点树 38 39FrameNode提供了节点的增、删、查、改的能力,能够修改非代理节点的子树结构。可以对所有FrameNode的节点的父子节点做出查询操作,并返回查询结果。 40 41> **说明:** 42> 43> 对节点进行增、删、改操作的时候,会对非法操作抛出异常信息。 44> 45> 通过查询获得的原生组件的代理节点,仅具备查询节点信息的作用,不具备修改节点属性的功能。代理节点不持有组件的实体节点,即不影响对应的节点的生命周期。 46> 47> 查询节点仅查询获得UI相关的节点,不返回语法节点。 48> 49> 使用自定义组件的场景下,可能查询获得自定义组件的新增节点,节点类型为“\_\_Common\_\_”。 50 51```ts 52import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI'; 53import { BusinessError } from '@kit.BasicServicesKit'; 54 55const TEST_TAG: string = "FrameNode" 56 57class Params { 58 text: string = "this is a text" 59} 60 61@Builder 62function buttonBuilder(params: Params) { 63 Column({ space: 10 }) { 64 Button(params.text) 65 .fontSize(12) 66 .borderRadius(8) 67 .borderWidth(2) 68 .backgroundColor(Color.Orange) 69 70 Button(params.text) 71 .fontSize(12) 72 .borderRadius(8) 73 .borderWidth(2) 74 .backgroundColor(Color.Pink) 75 } 76} 77 78class MyNodeController extends NodeController { 79 public buttonNode: BuilderNode<[Params]> | null = null; 80 public frameNode: FrameNode | null = null; 81 public childList: Array<FrameNode> = new Array<FrameNode>(); 82 public rootNode: FrameNode | null = null; 83 private uiContext: UIContext | null = null; 84 private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(buttonBuilder); 85 86 makeNode(uiContext: UIContext): FrameNode | null { 87 this.uiContext = uiContext; 88 if (this.rootNode == null) { 89 this.rootNode = new FrameNode(uiContext); 90 this.rootNode.commonAttribute 91 .width("50%") 92 .height(100) 93 .borderWidth(1) 94 .backgroundColor(Color.Gray) 95 } 96 97 if (this.frameNode == null) { 98 this.frameNode = new FrameNode(uiContext); 99 this.frameNode.commonAttribute 100 .width("100%") 101 .height(50) 102 .borderWidth(1) 103 .position({ x: 200, y: 0 }) 104 .backgroundColor(Color.Pink); 105 this.rootNode.appendChild(this.frameNode); 106 } 107 if (this.buttonNode == null) { 108 this.buttonNode = new BuilderNode<[Params]>(uiContext); 109 this.buttonNode.build(this.wrapBuilder, { text: "This is a Button" }) 110 this.rootNode.appendChild(this.buttonNode.getFrameNode()) 111 } 112 return this.rootNode; 113 } 114 115 operationFrameNodeWithFrameNode(frameNode: FrameNode | undefined | null) { 116 if (frameNode) { 117 console.log(TEST_TAG + " get ArkTSNode success.") 118 console.log(TEST_TAG + " check rootNode whether is modifiable " + frameNode.isModifiable()); 119 } 120 if (this.uiContext) { 121 let frameNode1 = new FrameNode(this.uiContext); 122 let frameNode2 = new FrameNode(this.uiContext); 123 frameNode1.commonAttribute.size({ width: 50, height: 50 }) 124 .backgroundColor(Color.Black) 125 .position({ x: 50, y: 60 }) 126 frameNode2.commonAttribute.size({ width: 50, height: 50 }) 127 .backgroundColor(Color.Orange) 128 .position({ x: 120, y: 60 }) 129 try { 130 frameNode?.appendChild(frameNode1); 131 console.log(TEST_TAG + " appendChild success "); 132 } catch (err) { 133 console.log(TEST_TAG + " appendChild fail :" + (err as BusinessError).code + " : " + (err as BusinessError).message); 134 } 135 try { 136 frameNode?.insertChildAfter(frameNode2, null); 137 console.log(TEST_TAG + " insertChildAfter success "); 138 } catch (err) { 139 console.log(TEST_TAG + " insertChildAfter fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message); 140 } 141 setTimeout(() => { 142 try { 143 frameNode?.removeChild(frameNode?.getChild(0)) 144 console.log(TEST_TAG + " removeChild success "); 145 } catch (err) { 146 console.log(TEST_TAG + " removeChild fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message); 147 } 148 }, 2000) 149 setTimeout(() => { 150 try { 151 frameNode?.clearChildren(); 152 console.log(TEST_TAG + " clearChildren success "); 153 } catch (err) { 154 console.log(TEST_TAG + " clearChildren fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message); 155 } 156 }, 4000) 157 } 158 } 159 160 testInterfaceAboutSearch(frameNode: FrameNode | undefined | null): string { 161 let result: string = ""; 162 if (frameNode) { 163 result = result + `current node is ${frameNode.getNodeType()} \n`; 164 result = result + `parent node is ${frameNode.getParent()?.getNodeType()} \n`; 165 result = result + `child count is ${frameNode.getChildrenCount()} \n`; 166 result = result + `first child node is ${frameNode.getFirstChild()?.getNodeType()} \n`; 167 result = result + `second child node is ${frameNode.getChild(1)?.getNodeType()} \n`; 168 result = result + `previousSibling node is ${frameNode.getPreviousSibling()?.getNodeType()} \n`; 169 result = result + `nextSibling node is ${frameNode.getNextSibling()?.getNodeType()} \n`; 170 } 171 return result; 172 } 173 174 checkAppendChild(parent: FrameNode | undefined | null, child: FrameNode | undefined | null) { 175 try { 176 if (parent && child) { 177 parent.appendChild(child); 178 console.log(TEST_TAG + " appendChild success "); 179 } 180 } catch (err) { 181 console.log(TEST_TAG + " appendChild fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message); 182 } 183 } 184} 185 186@Entry 187@Component 188struct Index { 189 @State index: number = 0; 190 @State result: string = "" 191 private myNodeController: MyNodeController = new MyNodeController(); 192 193 build() { 194 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { 195 List({ space: 20, initialIndex: 0 }) { 196 ListItem() { 197 Column({ space: 5 }) { 198 Text("验证FrameNode子节点的增、删、改功能") 199 Button("对自定义FrameNode进行操作") 200 .fontSize(16) 201 .width(400) 202 .onClick(() => { 203 // 对FrameNode节点进行增、删、改操作,正常实现。 204 this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.frameNode); 205 }) 206 Button("对BuilderNode中的代理节点进行操作") 207 .fontSize(16) 208 .width(400) 209 .onClick(() => { 210 // 对BuilderNode代理节点进行增、删、改操作,捕获异常信息。 211 this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.buttonNode?.getFrameNode()); 212 }) 213 Button("对原生组件中的代理节点进行操作") 214 .fontSize(16) 215 .width(400) 216 .onClick(() => { 217 // 对代理节点进行增、删、改操作,捕获异常信息。 218 this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.rootNode?.getParent()); 219 }) 220 } 221 } 222 223 ListItem() { 224 Column({ space: 5 }) { 225 Text("验证FrameNode添加子节点的特殊场景") 226 Button("新增BuilderNode的代理节点") 227 .fontSize(16) 228 .width(400) 229 .onClick(() => { 230 let buttonNode = new BuilderNode<[Params]>(this.getUIContext()); 231 buttonNode.build(wrapBuilder<[Params]>(buttonBuilder), { text: "BUTTON" }) 232 this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, buttonNode?.getFrameNode()); 233 }) 234 Button("新增原生组件代理节点") 235 .fontSize(16) 236 .width(400) 237 .onClick(() => { 238 this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, this.myNodeController?.rootNode?.getParent()); 239 }) 240 Button("新增已有父节点的自定义节点") 241 .fontSize(16) 242 .width(400) 243 .onClick(() => { 244 this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, this.myNodeController?.rootNode); 245 }) 246 } 247 } 248 249 ListItem() { 250 Column({ space: 5 }) { 251 Text("验证FrameNode节点的查询功能") 252 Button("对自定义FrameNode进行操作") 253 .fontSize(16) 254 .width(400) 255 .onClick(() => { 256 // 对FrameNode节点进行进行查询。当前节点为NodeContainer的子节点。 257 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode); 258 setTimeout(() => { 259 // 对FrameNode节点进行进行查询。rootNode下的第一个子节点。 260 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.frameNode); 261 }, 2000) 262 }) 263 Button("对BuilderNode中的代理节点进行操作") 264 .fontSize(16) 265 .width(400) 266 .onClick(() => { 267 // 对BuilderNode代理节点进行进行查询。当前节点为BuilderNode中的Column节点。 268 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.buttonNode?.getFrameNode()); 269 }) 270 Button("对原生组件中的代理节点进行操作") 271 .fontSize(16) 272 .width(400) 273 .onClick(() => { 274 // 对代理节点进行查询。当前节点为NodeContainer。 275 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode?.getParent()); 276 }) 277 } 278 } 279 }.height("50%") 280 281 Text(`Result:\n${this.result}`) 282 .fontSize(16) 283 .width(400) 284 .height(200) 285 .padding(30) 286 .borderWidth(1) 287 Column() { 288 Text("This is a NodeContainer.") 289 .textAlign(TextAlign.Center) 290 .borderRadius(10) 291 .backgroundColor(0xFFFFFF) 292 .width('100%') 293 .fontSize(16) 294 NodeContainer(this.myNodeController) 295 .borderWidth(1) 296 .width(400) 297 .height(150) 298 } 299 } 300 .padding({ left: 35, right: 35, top: 35, bottom: 35 }) 301 .width("100%") 302 .height("100%") 303 } 304} 305``` 306 307## 设置节点通用属性和事件回调 308 309FrameNode提供了[commonAttribute](../reference/apis-arkui/js-apis-arkui-frameNode.md#commonattribute12)和[commonEvent](../reference/apis-arkui/js-apis-arkui-frameNode.md#commonevent12)两个对象用于对设置节点的[通用属性](../reference/apis-arkui/arkui-ts/ts-universal-attributes-size.md)和[设置事件回调](../reference/apis-arkui/arkui-ts/ts-uicommonevent.md)。 310 311> **说明:** 312> 313> - 由于代理节点的属性不可修改,因此通过代理节点的commonAttribute修改节点的基础属性不生效。 314> 315> - 设置的基础事件与原生组件定义的事件平行,参与事件竞争。设置的基础事件不覆盖原生组件事件。同时设置两个事件回调的时候,优先回调原生组件事件。 316 317```ts 318import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI' 319 320class Params { 321 text: string = "this is a text" 322} 323 324@Builder 325function buttonBuilder(params: Params) { 326 Button(params.text) 327 .fontSize(12) 328 .borderRadius(8) 329 .borderWidth(2) 330 .backgroundColor(Color.Orange) 331 .onClick((event: ClickEvent) => { 332 console.log(`Button ${JSON.stringify(event)}`); 333 }) 334} 335 336class MyNodeController extends NodeController { 337 public buttonNode: BuilderNode<[Params]> | null = null; 338 public frameNode: FrameNode | null = null; 339 public rootNode: FrameNode | null = null; 340 private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(buttonBuilder); 341 342 makeNode(uiContext: UIContext): FrameNode | null { 343 if (this.rootNode == null) { 344 this.rootNode = new FrameNode(uiContext); 345 // 对rootNode进行属性修改,该节点为自定义的FrameNode节点,修改生效 346 this.rootNode.commonAttribute 347 .width("100%") 348 .height(100) 349 .borderWidth(1) 350 .backgroundColor(Color.Gray) 351 } 352 353 if (this.frameNode == null) { 354 this.frameNode = new FrameNode(uiContext); 355 // 对frameNode进行属性修改,该节点为自定义的FrameNode节点,修改生效 356 this.frameNode.commonAttribute 357 .width("50%") 358 .height(50) 359 .borderWidth(1) 360 .backgroundColor(Color.Pink); 361 this.rootNode.appendChild(this.frameNode); 362 } 363 if (this.buttonNode == null) { 364 this.buttonNode = new BuilderNode<[Params]>(uiContext); 365 this.buttonNode.build(this.wrapBuilder, { text: "This is a Button" }) 366 // 对BuilderNode中获取的FrameNode进行属性修改,该节点非自定义的FrameNode节点,修改不生效 367 this.buttonNode?.getFrameNode()?.commonAttribute.position({ x: 100, y: 100 }) 368 this.rootNode.appendChild(this.buttonNode.getFrameNode()) 369 } 370 return this.rootNode; 371 } 372 373 modifyNode(frameNode: FrameNode | null | undefined, sizeValue: SizeOptions, positionValue: Position) { 374 if (frameNode) { 375 frameNode.commonAttribute.size(sizeValue).position(positionValue); 376 } 377 } 378 379 addClickEvent(frameNode: FrameNode | null | undefined) { 380 if (frameNode) { 381 frameNode.commonEvent.setOnClick((event: ClickEvent) => { 382 console.log(`FrameNode ${JSON.stringify(event)}`); 383 }) 384 } 385 } 386} 387 388@Entry 389@Component 390struct Index { 391 private myNodeController: MyNodeController = new MyNodeController(); 392 393 build() { 394 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { 395 Column({ space: 10 }) { 396 Text("修改节点通用属性-宽高") 397 Button("modify ArkTS-FrameNode") 398 .onClick(() => { 399 // 获取到的是当前页面中的开发者创建的FrameNode对象,该节点可修改。即节点大小与位置。 400 console.log("Check the weather the node can be modified " + this.myNodeController?.frameNode 401 ?.isModifiable()); 402 this.myNodeController.modifyNode(this.myNodeController?.frameNode, { width: 150, height: 100 }, { 403 x: 100, 404 y: 0 405 }) 406 }) 407 Button("modify FrameNode get by BuilderNode") 408 .onClick(() => { 409 // 获取到的是当前页面中的BuilderNode的根节点,该节点不可修改。即节点大小与位置未发生改变。 410 console.log("Check the weather the node can be modified " + this.myNodeController?.buttonNode?.getFrameNode() 411 ?.isModifiable()); 412 this.myNodeController.modifyNode(this.myNodeController?.buttonNode?.getFrameNode(), { 413 width: 100, 414 height: 100 415 }, { x: 50, y: 50 }) 416 }) 417 Button("modify proxyFrameNode get by search") 418 .onClick(() => { 419 // rootNode调用getParent()获取到的是当前页面中的NodeContainer节点,该节点不可修改。即节点大小与位置未发生改变。 420 console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent() 421 ?.isModifiable()); 422 this.myNodeController.modifyNode(this.myNodeController?.rootNode?.getParent(), { 423 width: 500, 424 height: 500 425 }, { 426 x: 0, 427 y: 0 428 }) 429 }) 430 }.padding({ left: 35, right: 35, top: 35, bottom: 35 }) 431 432 Column({ space: 10 }) { 433 Text("修改节点点击事件") 434 Button("add click event to ArkTS-FrameNode") 435 .onClick(() => { 436 // 获取到的是当前页面中的开发者创建的FrameNode对象,该节点可增加点击事件。 437 // 增加的点击事件参与事件竞争,即点击事件会在该节点被消费且不不再向父组件冒泡。 438 console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent() 439 ?.isModifiable()); 440 this.myNodeController.addClickEvent(this.myNodeController?.frameNode) 441 }) 442 Button("add click event to FrameNode get by BuilderNode") 443 .onClick(() => { 444 // 获取到的是当前页面中的BuilderNode的根节点,该类节点可增加点击事件。 445 // 点击的时候优先回调通过原生组件接口设置的click事件回调,然后回调通过commonEvent增加的click监听。 446 console.log("Check the weather the node can be modified " + this.myNodeController?.buttonNode?.getFrameNode() 447 ?.isModifiable()); 448 this.myNodeController.addClickEvent(this.myNodeController?.buttonNode?.getFrameNode()) 449 }) 450 Button("add click event to proxyFrameNode get by search") 451 .onClick(() => { 452 // rootNode调用getParent()获取到的是当前页面中的NodeContainer节点,该类节点可增加点击事件。 453 console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent() 454 ?.isModifiable()); 455 this.myNodeController.addClickEvent(this.myNodeController?.rootNode?.getParent()); 456 }) 457 }.padding({ left: 35, right: 35, top: 35, bottom: 35 }) 458 459 NodeContainer(this.myNodeController) 460 .borderWidth(1) 461 .width("100%") 462 .height(100) 463 .onClick((event: ClickEvent) => { 464 console.log(`NodeContainer ${JSON.stringify(event)}`); 465 }) 466 } 467 .padding({ left: 35, right: 35, top: 35, bottom: 35 }) 468 .width("100%") 469 .height("100%") 470 } 471} 472``` 473 474## 自定义测量布局与绘制 475 476通过重写[onDraw](../reference/apis-arkui/js-apis-arkui-frameNode.md#ondraw12)方法,可以自定义FrameNode的绘制内容。[invalidate](../reference/apis-arkui/js-apis-arkui-frameNode.md#invalidate12)接口可以主动触发节点的重新绘制。 477 478通过重写[onMeasure](../reference/apis-arkui/js-apis-arkui-frameNode.md#onmeasure12)可以自定义FrameNode的测量方式,使用[measure](../reference/apis-arkui/js-apis-arkui-frameNode.md#measure12)可以主动传递布局约束触发重新测量。 479 480通过重写[onLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#onlayout12)方法可以自定义FrameNode的布局方式,使用[layout](../reference/apis-arkui/js-apis-arkui-frameNode.md#layout12)方法可以主动传递位置信息并触发重新布局。 481 482[setNeedsLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#setneedslayout12)可以将当前节点标记,在下一帧触发重新布局。 483 484> **说明:** 485> 486> - 对节点进行dispose解引用后,由于FrameNode对象不再对应一个实体节点,invalidate无法触发原有绑定节点的刷新。 487> 488> - 通过onDraw方法进行的自定义绘制,绘制内容大小无法超出组件大小。 489 490```ts 491import { DrawContext, FrameNode, NodeController, Position, Size, UIContext, LayoutConstraint } from '@kit.ArkUI'; 492import { drawing } from '@kit.ArkGraphics2D'; 493 494function GetChildLayoutConstraint(constraint: LayoutConstraint, child: FrameNode): LayoutConstraint { 495 const size = child.getUserConfigSize(); 496 const width = Math.max( 497 Math.min(constraint.maxSize.width, size.width.value), 498 constraint.minSize.width 499 ); 500 const height = Math.max( 501 Math.min(constraint.maxSize.height, size.height.value), 502 constraint.minSize.height 503 ); 504 const finalSize: Size = { width, height }; 505 const res: LayoutConstraint = { 506 maxSize: finalSize, 507 minSize: finalSize, 508 percentReference: finalSize 509 }; 510 511 return res; 512} 513 514class MyFrameNode extends FrameNode { 515 public width: number = 100; 516 public offsetY: number = 0; 517 private space: number = 1; 518 519 onMeasure(constraint: LayoutConstraint): void { 520 let sizeRes: Size = { width: vp2px(100), height: vp2px(100) }; 521 for (let i = 0;i < this.getChildrenCount(); i++) { 522 let child = this.getChild(i); 523 if (child) { 524 let childConstraint = GetChildLayoutConstraint(constraint, child); 525 child.measure(childConstraint); 526 let size = child.getMeasuredSize(); 527 sizeRes.height += size.height + this.space; 528 sizeRes.width = Math.max(sizeRes.width, size.width); 529 } 530 } 531 this.setMeasuredSize(sizeRes); 532 } 533 534 onLayout(position: Position): void { 535 let y = 0; 536 for (let i = 0;i < this.getChildrenCount(); i++) { 537 let child = this.getChild(i); 538 if (child) { 539 child.layout({ 540 x: vp2px(100), 541 y: vp2px(this.offsetY) 542 }); 543 y += child.getMeasuredSize().height + this.space; 544 } 545 } 546 this.setLayoutPosition(position); 547 } 548 549 onDraw(context: DrawContext) { 550 const canvas = context.canvas; 551 const pen = new drawing.Pen(); 552 pen.setStrokeWidth(15); 553 pen.setColor({ alpha: 255, red: 255, green: 0, blue: 0 }); 554 canvas.attachPen(pen); 555 canvas.drawRect({ 556 left: 50, 557 right: this.width + 50, 558 top: 50, 559 bottom: this.width + 50, 560 }); 561 canvas.detachPen(); 562 } 563 564 addWidth() { 565 this.width = (this.width + 10) % 50 + 100; 566 } 567} 568 569class MyNodeController extends NodeController { 570 public rootNode: MyFrameNode | null = null; 571 572 makeNode(context: UIContext): FrameNode | null { 573 this.rootNode = new MyFrameNode(context); 574 this.rootNode?.commonAttribute?.size({ width: 100, height: 100 }).backgroundColor(Color.Green); 575 let frameNode: FrameNode = new FrameNode(context); 576 this.rootNode.appendChild(frameNode); 577 frameNode.commonAttribute.width(10).height(10).backgroundColor(Color.Pink); 578 return this.rootNode; 579 } 580} 581 582@Entry 583@Component 584struct Index { 585 private nodeController: MyNodeController = new MyNodeController(); 586 587 build() { 588 Row() { 589 Column() { 590 NodeContainer(this.nodeController) 591 .width('100%') 592 .height(200) 593 .backgroundColor('#FFF0F0F0') 594 Button('Invalidate') 595 .margin(10) 596 .onClick(() => { 597 this.nodeController?.rootNode?.addWidth(); 598 this.nodeController?.rootNode?.invalidate(); 599 }) 600 Button('UpdateLayout') 601 .onClick(() => { 602 let node = this.nodeController.rootNode; 603 node!.offsetY = (node!.offsetY + 10) % 110; 604 this.nodeController?.rootNode?.setNeedsLayout(); 605 }) 606 } 607 .width('100%') 608 .height('100%') 609 } 610 .height('100%') 611 } 612} 613``` 614 615## 查找节点及获取基础信息 616 617FrameNode提供了查询接口用于返回实体节点的基础信息。具体返回的信息内容参考FrameNode中提供的接口。 618 619查找获得FrameNode的方式包括三种: 620 6211. 使用[getFrameNodeById](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyid12)获取。 622 6232. 使用[getFrameNodeByUniqueId](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyuniqueid12)获取。 624 6253. 通过[无感监听](../reference/apis-arkui/js-apis-arkui-observer.md)获取。 626 627> **说明:** 628> 629> 当前接口提供的可查询的信息包括: 630> 631> - 节点大小:[getMeasuredSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getmeasuredsize12),[getUserConfigSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigsize12) 632> 633> - 布局信息:[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) 634> 635> - 节点信息:[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) 636