# 自定义组件节点 (FrameNode) ## 概述 对于拥有自定义å‰ç«¯çš„第三方框架(如JSONã€XMLã€DOMæ ‘ç‰ï¼‰ï¼Œéœ€å°†ç‰¹å®šçš„DSL转æ¢ä¸ºArkUIçš„å£°æ˜Žå¼æè¿°ã€‚å¦‚ä¸‹å›¾æè¿°äº†JSON定义的å‰ç«¯æ¡†æž¶å’ŒArkUIå£°æ˜Žå¼æè¿°çš„å¯¹åº”å…³ç³»ã€‚  上述转æ¢è¿‡ç¨‹éœ€è¦ä¾èµ–é¢å¤–的数æ®é©±åŠ¨ï¼Œç»‘å®šè‡³[Builder](../quick-start/arkts-builder.md)ä¸ï¼Œè¾ƒä¸ºå¤æ‚ä¸”æ€§èƒ½æ¬ ä½³ã€‚è¿™ç±»æ¡†æž¶é€šå¸¸ä¾èµ–于ArkUI的布局ã€äº‹ä»¶å¤„ç†ã€åŸºç¡€çš„节点æ“作和自定义能力。大部分组件通过自定义实现,但需结åˆä½¿ç”¨éƒ¨åˆ†ç³»ç»Ÿç»„ä»¶ä»¥å®žçŽ°æ··åˆæ˜¾ç¤ºï¼Œå¦‚下图示例既使用了FrameNode的自定义方法进行绘制,åˆä½¿ç”¨äº†ç³»ç»Ÿç»„ä»¶ColumnåŠå…¶å组件Text,通过BuilderNode的方å¼å°†å…¶æŒ‚è½½åˆ°æ ¹èŠ‚ç‚¹çš„FrameNodeä¸Šæ··åˆæ˜¾ç¤ºã€‚  [FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md)的设计åˆè¡·æ£æ˜¯ä¸ºäº†è§£å†³ä¸Šè¿°è½¬æ¢é—®é¢˜ã€‚FrameNodeè¡¨ç¤ºç»„ä»¶æ ‘ä¸çš„实体节点,与自定义å ä½å®¹å™¨ç»„ä»¶[NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md)相é…åˆï¼Œå®žçŽ°åœ¨å ä½å®¹å™¨å†…æž„å»ºä¸€æ£µè‡ªå®šä¹‰çš„èŠ‚ç‚¹æ ‘ã€‚è¯¥èŠ‚ç‚¹æ ‘æ”¯æŒåŠ¨æ€æ“ä½œï¼Œå¦‚èŠ‚ç‚¹çš„å¢žåŠ ã€ä¿®æ”¹å’Œåˆ 除。基础的FrameNodeå…·å¤‡è®¾ç½®é€šç”¨å±žæ€§å’Œäº‹ä»¶å›žè°ƒçš„åŠŸèƒ½ï¼ŒåŒæ—¶æä¾›å®Œæ•´çš„自定义能力,涵盖自定义测é‡ã€å¸ƒå±€å’Œç»˜åˆ¶ç‰æ–¹é¢ã€‚ 除æ¤ä¹‹å¤–,ArkUI还æä¾›äº†èŽ·å–å’Œé历系统组件对应代ç†FrameNode对象的能力(下文简称代ç†èŠ‚ç‚¹ï¼‰ã€‚ä»£ç†èŠ‚ç‚¹èƒ½å¤Ÿç”¨äºŽé历整个UIçš„æ ‘å½¢ç»“æž„ï¼Œæ”¯æŒèŽ·å–系统组件节点的详细信æ¯ï¼Œä»¥åŠé¢å¤–注册组件的事件监å¬å›žè°ƒã€‚ ## åˆ›å»ºå’Œåˆ é™¤èŠ‚ç‚¹ FrameNodeæä¾›äº†èŠ‚ç‚¹åˆ›å»ºå’Œåˆ é™¤çš„èƒ½åŠ›ã€‚å¯ä»¥é€šè¿‡FrameNodeçš„æž„é€ å‡½æ•°åˆ›å»ºè‡ªå®šä¹‰FrameNodeèŠ‚ç‚¹ï¼Œé€šè¿‡æž„é€ å‡½æ•°åˆ›å»ºçš„èŠ‚ç‚¹å¯¹åº”ä¸€ä¸ªå®žä½“çš„èŠ‚ç‚¹ã€‚åŒæ—¶ï¼Œå¯ä»¥é€šè¿‡FrameNodeä¸çš„[dispose](../reference/apis-arkui/js-apis-arkui-frameNode.md#dispose12)æŽ¥å£æ¥å®žçŽ°ä¸Žå®žä½“èŠ‚ç‚¹çš„ç»‘å®šå…³ç³»çš„è§£é™¤ã€‚ > **说明:** > > - 在创建FrameNode对象的时候需è¦ä¼ å…¥å¿…é€‰å‚æ•°UIContextï¼Œè‹¥æœªä¼ å…¥UIContextå¯¹è±¡æˆ–è€…ä¼ å…¥ä¸åˆæ³•,则节点创建抛出异常。 > > - 自定义å ä½ç»„件将节点进行显示的时候需è¦ä¿è¯UI上下文一致,å¦åˆ™ä¼šå‡ºçŽ°æ˜¾ç¤ºå¼‚å¸¸ã€‚ > > - è‹¥ä¸æŒæœ‰FrameNode对象,则该对象会在GC的时候被回收。 ## 判æ–节点是å¦å¯ä¿®æ”¹ [isModifiable](../reference/apis-arkui/js-apis-arkui-frameNode.md#ismodifiable12)用于查询当å‰èŠ‚ç‚¹ç±»åž‹æ˜¯å¦ä¸ºç³»ç»Ÿç»„件的代ç†èŠ‚ç‚¹ã€‚å½“FrameNode节点作为系统组件的代ç†èŠ‚ç‚¹çš„æ—¶å€™ï¼Œè¯¥èŠ‚ç‚¹ä¸å¯ä¿®æ”¹ã€‚峿— 法修改代ç†èŠ‚ç‚¹çš„è‡ªèº«å±žæ€§ä»¥åŠå…¶å节点的结构。 ## 获å–对应的RenderNode节点 FrameNodeæä¾›äº†[getRenderNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#getrendernode)接å£ï¼Œç”¨äºŽèŽ·å–FrameNodeä¸çš„RenderNode。å¯ä»¥é€šè¿‡å¯¹èŽ·å–到的RenderNode对象进行æ“作,动æ€ä¿®æ”¹FrameNode上绘制相关的属性,具体å¯ä¿®æ”¹çš„属性å‚考[RenderNode](arkts-user-defined-arktsNode-renderNode.md)的接å£ã€‚ > **说明:** > > - æ— æ³•èŽ·å–系统组件代ç†FrameNodeçš„RenderNode对象。 > > - BuilderNodeä¸è°ƒç”¨[getFrameNode](../reference/apis-arkui/js-apis-arkui-builderNode.md#getframenode)获å–得到的FrameNode节点对象ä¸ï¼Œå¯ä»¥é€šè¿‡getRenderNode获å–å¯¹åº”çš„æ ¹èŠ‚ç‚¹çš„RenderNode对象。 ## æ“ä½œèŠ‚ç‚¹æ ‘ FrameNodeæä¾›äº†èŠ‚ç‚¹çš„å¢žã€åˆ ã€æŸ¥ã€æ”¹çš„能力,能够修改éžä»£ç†èŠ‚ç‚¹çš„åæ ‘结构。å¯ä»¥å¯¹æ‰€æœ‰FrameNode的节点的父å节点åšå‡ºæŸ¥è¯¢æ“作,并返回查询结果。 > **说明:** > > 对节点进行增ã€åˆ ã€æ”¹æ“ä½œçš„æ—¶å€™ï¼Œä¼šå¯¹éžæ³•æ“作抛出异常信æ¯ã€‚ > > 通过查询获得的原生组件的代ç†èŠ‚ç‚¹ï¼Œä»…å…·å¤‡æŸ¥è¯¢èŠ‚ç‚¹ä¿¡æ¯çš„作用,ä¸å…·å¤‡ä¿®æ”¹èŠ‚ç‚¹å±žæ€§çš„åŠŸèƒ½ã€‚ä»£ç†èŠ‚ç‚¹ä¸æŒæœ‰ç»„件的实体节点,å³ä¸å½±å“对应的节点的生命周期。 > > 查询节点仅查询获得UI相关的节点,ä¸è¿”å›žè¯æ³•节点。 > > 使用自定义组件的场景下,å¯èƒ½æŸ¥è¯¢èŽ·å¾—è‡ªå®šä¹‰ç»„ä»¶çš„æ–°å¢žèŠ‚ç‚¹ï¼ŒèŠ‚ç‚¹ç±»åž‹ä¸ºâ€œ\_\_Common\_\_â€ã€‚ ```ts import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI'; import { BusinessError } from '@kit.BasicServicesKit'; const TEST_TAG: string = "FrameNode" class Params { text: string = "this is a text" } @Builder function buttonBuilder(params: Params) { Column({ space: 10 }) { Button(params.text) .fontSize(12) .borderRadius(8) .borderWidth(2) .backgroundColor(Color.Orange) Button(params.text) .fontSize(12) .borderRadius(8) .borderWidth(2) .backgroundColor(Color.Pink) } } class MyNodeController extends NodeController { public buttonNode: BuilderNode<[Params]> | null = null; public frameNode: FrameNode | null = null; public childList: Array<FrameNode> = new Array<FrameNode>(); public rootNode: FrameNode | null = null; private uiContext: UIContext | null = null; private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(buttonBuilder); makeNode(uiContext: UIContext): FrameNode | null { this.uiContext = uiContext; if (this.rootNode == null) { this.rootNode = new FrameNode(uiContext); this.rootNode.commonAttribute .width("50%") .height(100) .borderWidth(1) .backgroundColor(Color.Gray) } if (this.frameNode == null) { this.frameNode = new FrameNode(uiContext); this.frameNode.commonAttribute .width("100%") .height(50) .borderWidth(1) .position({ x: 200, y: 0 }) .backgroundColor(Color.Pink); this.rootNode.appendChild(this.frameNode); } if (this.buttonNode == null) { this.buttonNode = new BuilderNode<[Params]>(uiContext); this.buttonNode.build(this.wrapBuilder, { text: "This is a Button" }) this.rootNode.appendChild(this.buttonNode.getFrameNode()) } return this.rootNode; } operationFrameNodeWithFrameNode(frameNode: FrameNode | undefined | null) { if (frameNode) { console.log(TEST_TAG + " get ArkTSNode success.") console.log(TEST_TAG + " check rootNode whether is modifiable " + frameNode.isModifiable()); } if (this.uiContext) { let frameNode1 = new FrameNode(this.uiContext); let frameNode2 = new FrameNode(this.uiContext); frameNode1.commonAttribute.size({ width: 50, height: 50 }) .backgroundColor(Color.Black) .position({ x: 50, y: 60 }) frameNode2.commonAttribute.size({ width: 50, height: 50 }) .backgroundColor(Color.Orange) .position({ x: 120, y: 60 }) try { frameNode?.appendChild(frameNode1); console.log(TEST_TAG + " appendChild success "); } catch (err) { console.log(TEST_TAG + " appendChild fail :" + (err as BusinessError).code + " : " + (err as BusinessError).message); } try { frameNode?.insertChildAfter(frameNode2, null); console.log(TEST_TAG + " insertChildAfter success "); } catch (err) { console.log(TEST_TAG + " insertChildAfter fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message); } setTimeout(() => { try { frameNode?.removeChild(frameNode?.getChild(0)) console.log(TEST_TAG + " removeChild success "); } catch (err) { console.log(TEST_TAG + " removeChild fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message); } }, 2000) setTimeout(() => { try { frameNode?.clearChildren(); console.log(TEST_TAG + " clearChildren success "); } catch (err) { console.log(TEST_TAG + " clearChildren fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message); } }, 4000) } } testInterfaceAboutSearch(frameNode: FrameNode | undefined | null): string { let result: string = ""; if (frameNode) { result = result + `current node is ${frameNode.getNodeType()} \n`; result = result + `parent node is ${frameNode.getParent()?.getNodeType()} \n`; result = result + `child count is ${frameNode.getChildrenCount()} \n`; result = result + `first child node is ${frameNode.getFirstChild()?.getNodeType()} \n`; result = result + `second child node is ${frameNode.getChild(1)?.getNodeType()} \n`; result = result + `previousSibling node is ${frameNode.getPreviousSibling()?.getNodeType()} \n`; result = result + `nextSibling node is ${frameNode.getNextSibling()?.getNodeType()} \n`; } return result; } checkAppendChild(parent: FrameNode | undefined | null, child: FrameNode | undefined | null) { try { if (parent && child) { parent.appendChild(child); console.log(TEST_TAG + " appendChild success "); } } catch (err) { console.log(TEST_TAG + " appendChild fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message); } } } @Entry @Component struct Index { @State index: number = 0; @State result: string = "" private myNodeController: MyNodeController = new MyNodeController(); build() { Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { List({ space: 20, initialIndex: 0 }) { ListItem() { Column({ space: 5 }) { Text("验è¯FrameNodeå节点的增ã€åˆ ã€æ”¹åŠŸèƒ½") Button("对自定义FrameNode进行æ“作") .fontSize(16) .width(400) .onClick(() => { // 对FrameNode节点进行增ã€åˆ ã€æ”¹æ“作,æ£å¸¸å®žçŽ°ã€‚ this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.frameNode); }) Button("对BuilderNodeä¸çš„代ç†èŠ‚ç‚¹è¿›è¡Œæ“作") .fontSize(16) .width(400) .onClick(() => { // 对BuilderNode代ç†èŠ‚ç‚¹è¿›è¡Œå¢žã€åˆ ã€æ”¹æ“作,æ•获异常信æ¯ã€‚ this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.buttonNode?.getFrameNode()); }) Button("对原生组件ä¸çš„代ç†èŠ‚ç‚¹è¿›è¡Œæ“作") .fontSize(16) .width(400) .onClick(() => { // 对代ç†èŠ‚ç‚¹è¿›è¡Œå¢žã€åˆ ã€æ”¹æ“作,æ•获异常信æ¯ã€‚ this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.rootNode?.getParent()); }) } } ListItem() { Column({ space: 5 }) { Text("验è¯FrameNodeæ·»åŠ å节点的特殊场景") Button("新增BuilderNode的代ç†èŠ‚ç‚¹") .fontSize(16) .width(400) .onClick(() => { let buttonNode = new BuilderNode<[Params]>(this.getUIContext()); buttonNode.build(wrapBuilder<[Params]>(buttonBuilder), { text: "BUTTON" }) this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, buttonNode?.getFrameNode()); }) Button("新增原生组件代ç†èŠ‚ç‚¹") .fontSize(16) .width(400) .onClick(() => { this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, this.myNodeController?.rootNode?.getParent()); }) Button("新增已有父节点的自定义节点") .fontSize(16) .width(400) .onClick(() => { this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, this.myNodeController?.rootNode); }) } } ListItem() { Column({ space: 5 }) { Text("验è¯FrameNode节点的查询功能") Button("对自定义FrameNode进行æ“作") .fontSize(16) .width(400) .onClick(() => { // 对FrameNode节点进行进行查询。当å‰èŠ‚ç‚¹ä¸ºNodeContainerçš„å节点。 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode); setTimeout(() => { // 对FrameNode节点进行进行查询。rootNode下的第一个å节点。 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.frameNode); }, 2000) }) Button("对BuilderNodeä¸çš„代ç†èŠ‚ç‚¹è¿›è¡Œæ“作") .fontSize(16) .width(400) .onClick(() => { // 对BuilderNode代ç†èŠ‚ç‚¹è¿›è¡Œè¿›è¡ŒæŸ¥è¯¢ã€‚å½“å‰èŠ‚ç‚¹ä¸ºBuilderNodeä¸çš„Column节点。 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.buttonNode?.getFrameNode()); }) Button("对原生组件ä¸çš„代ç†èŠ‚ç‚¹è¿›è¡Œæ“作") .fontSize(16) .width(400) .onClick(() => { // 对代ç†èŠ‚ç‚¹è¿›è¡ŒæŸ¥è¯¢ã€‚å½“å‰èŠ‚ç‚¹ä¸ºNodeContainer。 this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode?.getParent()); }) } } }.height("50%") Text(`Result:\n${this.result}`) .fontSize(16) .width(400) .height(200) .padding(30) .borderWidth(1) Column() { Text("This is a NodeContainer.") .textAlign(TextAlign.Center) .borderRadius(10) .backgroundColor(0xFFFFFF) .width('100%') .fontSize(16) NodeContainer(this.myNodeController) .borderWidth(1) .width(400) .height(150) } } .padding({ left: 35, right: 35, top: 35, bottom: 35 }) .width("100%") .height("100%") } } ``` ## 设置节点通用属性和事件回调 FrameNodeæä¾›äº†[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)。 > **说明:** > > - 由于代ç†èŠ‚ç‚¹çš„å±žæ€§ä¸å¯ä¿®æ”¹ï¼Œå› æ¤é€šè¿‡ä»£ç†èŠ‚ç‚¹çš„commonAttribute修改节点的基础属性ä¸ç”Ÿæ•ˆã€‚ > > - 设置的基础事件与原生组件定义的事件平行,å‚与事件竞争。设置的基础事件ä¸è¦†ç›–åŽŸç”Ÿç»„ä»¶äº‹ä»¶ã€‚åŒæ—¶è®¾ç½®ä¸¤ä¸ªäº‹ä»¶å›žè°ƒçš„æ—¶å€™ï¼Œä¼˜å…ˆå›žè°ƒåŽŸç”Ÿç»„ä»¶äº‹ä»¶ã€‚ ```ts import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI' class Params { text: string = "this is a text" } @Builder function buttonBuilder(params: Params) { Button(params.text) .fontSize(12) .borderRadius(8) .borderWidth(2) .backgroundColor(Color.Orange) .onClick((event: ClickEvent) => { console.log(`Button ${JSON.stringify(event)}`); }) } class MyNodeController extends NodeController { public buttonNode: BuilderNode<[Params]> | null = null; public frameNode: FrameNode | null = null; public rootNode: FrameNode | null = null; private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(buttonBuilder); makeNode(uiContext: UIContext): FrameNode | null { if (this.rootNode == null) { this.rootNode = new FrameNode(uiContext); // 对rootNode进行属性修改,该节点为自定义的FrameNode节点,修改生效 this.rootNode.commonAttribute .width("100%") .height(100) .borderWidth(1) .backgroundColor(Color.Gray) } if (this.frameNode == null) { this.frameNode = new FrameNode(uiContext); // 对frameNode进行属性修改,该节点为自定义的FrameNode节点,修改生效 this.frameNode.commonAttribute .width("50%") .height(50) .borderWidth(1) .backgroundColor(Color.Pink); this.rootNode.appendChild(this.frameNode); } if (this.buttonNode == null) { this.buttonNode = new BuilderNode<[Params]>(uiContext); this.buttonNode.build(this.wrapBuilder, { text: "This is a Button" }) // 对BuilderNodeä¸èŽ·å–çš„FrameNode进行属性修改,该节点éžè‡ªå®šä¹‰çš„FrameNode节点,修改ä¸ç”Ÿæ•ˆ this.buttonNode?.getFrameNode()?.commonAttribute.position({ x: 100, y: 100 }) this.rootNode.appendChild(this.buttonNode.getFrameNode()) } return this.rootNode; } modifyNode(frameNode: FrameNode | null | undefined, sizeValue: SizeOptions, positionValue: Position) { if (frameNode) { frameNode.commonAttribute.size(sizeValue).position(positionValue); } } addClickEvent(frameNode: FrameNode | null | undefined) { if (frameNode) { frameNode.commonEvent.setOnClick((event: ClickEvent) => { console.log(`FrameNode ${JSON.stringify(event)}`); }) } } } @Entry @Component struct Index { private myNodeController: MyNodeController = new MyNodeController(); build() { Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { Column({ space: 10 }) { Text("修改节点通用属性-宽高") Button("modify ArkTS-FrameNode") .onClick(() => { // 获å–到的是当å‰é¡µé¢ä¸çš„å¼€å‘者创建的FrameNode对象,该节点å¯ä¿®æ”¹ã€‚å³èŠ‚ç‚¹å¤§å°ä¸Žä½ç½®ã€‚ console.log("Check the weather the node can be modified " + this.myNodeController?.frameNode ?.isModifiable()); this.myNodeController.modifyNode(this.myNodeController?.frameNode, { width: 150, height: 100 }, { x: 100, y: 0 }) }) Button("modify FrameNode get by BuilderNode") .onClick(() => { // 获å–到的是当å‰é¡µé¢ä¸çš„BuilderNodeçš„æ ¹èŠ‚ç‚¹ï¼Œè¯¥èŠ‚ç‚¹ä¸å¯ä¿®æ”¹ã€‚å³èŠ‚ç‚¹å¤§å°ä¸Žä½ç½®æœªå‘生改å˜ã€‚ console.log("Check the weather the node can be modified " + this.myNodeController?.buttonNode?.getFrameNode() ?.isModifiable()); this.myNodeController.modifyNode(this.myNodeController?.buttonNode?.getFrameNode(), { width: 100, height: 100 }, { x: 50, y: 50 }) }) Button("modify proxyFrameNode get by search") .onClick(() => { // rootNode调用getParent()获å–到的是当å‰é¡µé¢ä¸çš„NodeContainer节点,该节点ä¸å¯ä¿®æ”¹ã€‚å³èŠ‚ç‚¹å¤§å°ä¸Žä½ç½®æœªå‘生改å˜ã€‚ console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent() ?.isModifiable()); this.myNodeController.modifyNode(this.myNodeController?.rootNode?.getParent(), { width: 500, height: 500 }, { x: 0, y: 0 }) }) }.padding({ left: 35, right: 35, top: 35, bottom: 35 }) Column({ space: 10 }) { Text("修改节点点击事件") Button("add click event to ArkTS-FrameNode") .onClick(() => { // 获å–到的是当å‰é¡µé¢ä¸çš„å¼€å‘者创建的FrameNode对象,该节点å¯å¢žåŠ ç‚¹å‡»äº‹ä»¶ã€‚ // å¢žåŠ çš„ç‚¹å‡»äº‹ä»¶å‚与事件竞争,å³ç‚¹å‡»äº‹ä»¶ä¼šåœ¨è¯¥èŠ‚ç‚¹è¢«æ¶ˆè´¹ä¸”ä¸ä¸å†å‘父组件冒泡。 console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent() ?.isModifiable()); this.myNodeController.addClickEvent(this.myNodeController?.frameNode) }) Button("add click event to FrameNode get by BuilderNode") .onClick(() => { // 获å–到的是当å‰é¡µé¢ä¸çš„BuilderNodeçš„æ ¹èŠ‚ç‚¹ï¼Œè¯¥ç±»èŠ‚ç‚¹å¯å¢žåŠ ç‚¹å‡»äº‹ä»¶ã€‚ // 点击的时候优先回调通过原生组件接å£è®¾ç½®çš„click事件回调,然åŽå›žè°ƒé€šè¿‡commonEventå¢žåŠ çš„click监å¬ã€‚ console.log("Check the weather the node can be modified " + this.myNodeController?.buttonNode?.getFrameNode() ?.isModifiable()); this.myNodeController.addClickEvent(this.myNodeController?.buttonNode?.getFrameNode()) }) Button("add click event to proxyFrameNode get by search") .onClick(() => { // rootNode调用getParent()获å–到的是当å‰é¡µé¢ä¸çš„NodeContainer节点,该类节点å¯å¢žåŠ ç‚¹å‡»äº‹ä»¶ã€‚ console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent() ?.isModifiable()); this.myNodeController.addClickEvent(this.myNodeController?.rootNode?.getParent()); }) }.padding({ left: 35, right: 35, top: 35, bottom: 35 }) NodeContainer(this.myNodeController) .borderWidth(1) .width("100%") .height(100) .onClick((event: ClickEvent) => { console.log(`NodeContainer ${JSON.stringify(event)}`); }) } .padding({ left: 35, right: 35, top: 35, bottom: 35 }) .width("100%") .height("100%") } } ``` ## 自定义测é‡å¸ƒå±€ä¸Žç»˜åˆ¶ 通过é‡å†™[onDraw](../reference/apis-arkui/js-apis-arkui-frameNode.md#ondraw12)方法,å¯ä»¥è‡ªå®šä¹‰FrameNode的绘制内容。[invalidate](../reference/apis-arkui/js-apis-arkui-frameNode.md#invalidate12)接å£å¯ä»¥ä¸»åŠ¨è§¦å‘èŠ‚ç‚¹çš„é‡æ–°ç»˜åˆ¶ã€‚ 通过é‡å†™[onMeasure](../reference/apis-arkui/js-apis-arkui-frameNode.md#onmeasure12)å¯ä»¥è‡ªå®šä¹‰FrameNodeçš„æµ‹é‡æ–¹å¼ï¼Œä½¿ç”¨[measure](../reference/apis-arkui/js-apis-arkui-frameNode.md#measure12)å¯ä»¥ä¸»åŠ¨ä¼ é€’å¸ƒå±€çº¦æŸè§¦å‘釿–°æµ‹é‡ã€‚ 通过é‡å†™[onLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#onlayout12)方法å¯ä»¥è‡ªå®šä¹‰FrameNode的布局方å¼ï¼Œä½¿ç”¨[layout](../reference/apis-arkui/js-apis-arkui-frameNode.md#layout12)方法å¯ä»¥ä¸»åŠ¨ä¼ é€’ä½ç½®ä¿¡æ¯å¹¶è§¦å‘釿–°å¸ƒå±€ã€‚ [setNeedsLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#setneedslayout12)å¯ä»¥å°†å½“å‰èŠ‚ç‚¹æ ‡è®°ï¼Œåœ¨ä¸‹ä¸€å¸§è§¦å‘釿–°å¸ƒå±€ã€‚ > **说明:** > > - 对节点进行dispose解引用åŽï¼Œç”±äºŽFrameNode对象ä¸å†å¯¹åº”一个实体节点,invalidateæ— æ³•è§¦å‘原有绑定节点的刷新。 > > - 通过onDrawæ–¹æ³•è¿›è¡Œçš„è‡ªå®šä¹‰ç»˜åˆ¶ï¼Œç»˜åˆ¶å†…å®¹å¤§å°æ— 法超出组件大å°ã€‚ ```ts import { DrawContext, FrameNode, NodeController, Position, Size, UIContext, LayoutConstraint } from '@kit.ArkUI'; import { drawing } from '@kit.ArkGraphics2D'; function GetChildLayoutConstraint(constraint: LayoutConstraint, child: FrameNode): LayoutConstraint { const size = child.getUserConfigSize(); const width = Math.max( Math.min(constraint.maxSize.width, size.width.value), constraint.minSize.width ); const height = Math.max( Math.min(constraint.maxSize.height, size.height.value), constraint.minSize.height ); const finalSize: Size = { width, height }; const res: LayoutConstraint = { maxSize: finalSize, minSize: finalSize, percentReference: finalSize }; return res; } class MyFrameNode extends FrameNode { public width: number = 100; public offsetY: number = 0; private space: number = 1; onMeasure(constraint: LayoutConstraint): void { let sizeRes: Size = { width: vp2px(100), height: vp2px(100) }; for (let i = 0;i < this.getChildrenCount(); i++) { let child = this.getChild(i); if (child) { let childConstraint = GetChildLayoutConstraint(constraint, child); child.measure(childConstraint); let size = child.getMeasuredSize(); sizeRes.height += size.height + this.space; sizeRes.width = Math.max(sizeRes.width, size.width); } } this.setMeasuredSize(sizeRes); } onLayout(position: Position): void { for (let i = 0;i < this.getChildrenCount(); i++) { let child = this.getChild(i); if (child) { child.layout({ x: vp2px(100), y: vp2px(this.offsetY) }); let layoutPosition = child.getLayoutPosition(); console.log("child position:" + JSON.stringify(layoutPosition)); } } this.setLayoutPosition(position); } onDraw(context: DrawContext) { const canvas = context.canvas; const pen = new drawing.Pen(); pen.setStrokeWidth(15); pen.setColor({ alpha: 255, red: 255, green: 0, blue: 0 }); canvas.attachPen(pen); canvas.drawRect({ left: 50, right: this.width + 50, top: 50, bottom: this.width + 50, }); canvas.detachPen(); } addWidth() { this.width = (this.width + 10) % 50 + 100; } } class MyNodeController extends NodeController { public rootNode: MyFrameNode | null = null; makeNode(context: UIContext): FrameNode | null { this.rootNode = new MyFrameNode(context); this.rootNode?.commonAttribute?.size({ width: 100, height: 100 }).backgroundColor(Color.Green); let frameNode: FrameNode = new FrameNode(context); this.rootNode.appendChild(frameNode); frameNode.commonAttribute.width(10).height(10).backgroundColor(Color.Pink); return this.rootNode; } } @Entry @Component struct Index { private nodeController: MyNodeController = new MyNodeController(); build() { Row() { Column() { NodeContainer(this.nodeController) .width('100%') .height(200) .backgroundColor('#FFF0F0F0') Button('Invalidate') .margin(10) .onClick(() => { this.nodeController?.rootNode?.addWidth(); this.nodeController?.rootNode?.invalidate(); }) Button('UpdateLayout') .onClick(() => { let node = this.nodeController.rootNode; node!.offsetY = (node!.offsetY + 10) % 110; this.nodeController?.rootNode?.setNeedsLayout(); }) } .width('100%') .height('100%') } .height('100%') } } ``` ## 查找节点åŠèŽ·å–åŸºç¡€ä¿¡æ¯ FrameNodeæä¾›äº†æŸ¥è¯¢æŽ¥å£ç”¨äºŽè¿”回实体节点的基础信æ¯ã€‚具体返回的信æ¯å†…容å‚考FrameNodeä¸æä¾›çš„æŽ¥å£ã€‚ 查找获得FrameNode的方å¼åŒ…括三ç§ï¼š 1. 使用[getFrameNodeById](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyid12)获å–。 2. 使用[getFrameNodeByUniqueId](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyuniqueid12)获å–。 3. 通过[æ— æ„Ÿç›‘å¬](../reference/apis-arkui/js-apis-arkui-observer.md)获å–。 > **说明:** > > 1ã€å½“å‰æŽ¥å£æä¾›çš„å¯æŸ¥è¯¢çš„ä¿¡æ¯åŒ…括: > > - 节点大å°ï¼š[getMeasuredSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getmeasuredsize12),[getUserConfigSize](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuserconfigsize12) > > - 布局信æ¯ï¼š[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) > > - 节点信æ¯ï¼š[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) > > 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组件ç‰ã€‚ ## 获å–节点ä½ç½®åç§»ä¿¡æ¯ FrameNodeæä¾›äº†æŸ¥è¯¢èŠ‚ç‚¹ç›¸å¯¹çª—å£ã€çˆ¶ç»„件以åŠå±å¹•ä½ç½®åç§»çš„ä¿¡æ¯æŽ¥å£ï¼ˆ[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))。 ```ts import { NodeController, FrameNode, UIContext } from '@kit.ArkUI'; const TEST_TAG : string = "FrameNode" class MyNodeController extends NodeController { public frameNode: FrameNode | null = null; private rootNode: FrameNode | null = null; makeNode(uiContext: UIContext): FrameNode | null { this.rootNode = new FrameNode(uiContext); this.frameNode = new FrameNode(uiContext); this.rootNode.appendChild(this.frameNode); return this.rootNode; } getPositionToWindow() { let positionToWindow = this.rootNode?.getPositionToWindow(); // 获å–FrameNode相对于窗å£çš„ä½ç½®åç§» console.log(TEST_TAG + JSON.stringify(positionToWindow)); } getPositionToParent() { let positionToParent = this.rootNode?.getPositionToParent(); // 获å–FrameNode相对于父组件的ä½ç½®åç§» console.log(TEST_TAG + JSON.stringify(positionToParent)); } getPositionToScreen() { let positionToScreen = this.rootNode?.getPositionToScreen(); // 获å–FrameNode相对于å±å¹•çš„ä½ç½®åç§» console.log(TEST_TAG + JSON.stringify(positionToScreen)); } getPositionToWindowWithTransform() { let positionToWindowWithTransform = this.rootNode?.getPositionToWindowWithTransform(); // 获å–FrameNode相对于窗å£å¸¦æœ‰ç»˜åˆ¶å±žæ€§çš„ä½ç½®åç§» console.log(TEST_TAG + JSON.stringify(positionToWindowWithTransform)); } getPositionToParentWithTransform() { let positionToParentWithTransform = this.rootNode?.getPositionToParentWithTransform(); // 获å–FrameNode相对于父组件带有绘制属性的ä½ç½®åç§» console.log(TEST_TAG + JSON.stringify(positionToParentWithTransform)); } getPositionToScreenWithTransform() { let positionToScreenWithTransform = this.rootNode?.getPositionToScreenWithTransform(); // 获å–FrameNode相对于å±å¹•带有绘制属性的ä½ç½®åç§» console.log(TEST_TAG + JSON.stringify(positionToScreenWithTransform)); } } @Entry @Component struct Index { private myNodeController: MyNodeController = new MyNodeController(); build() { Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { Button("getPositionToWindow") .width(300) .onClick(()=>{ this.myNodeController.getPositionToWindow(); }) Button("getPositionToParent") .width(300) .onClick(()=>{ this.myNodeController.getPositionToParent(); }) Button("getPositionToScreen") .width(300) .onClick(()=>{ this.myNodeController.getPositionToScreen(); }) Button("getPositionToParentWithTransform") .width(300) .onClick(()=>{ this.myNodeController.getPositionToParentWithTransform(); }) Button("getPositionToWindowWithTransform") .width(300) .onClick(()=>{ this.myNodeController.getPositionToWindowWithTransform(); }) Button("getPositionToScreenWithTransform") .width(300) .onClick(()=>{ this.myNodeController.getPositionToScreenWithTransform(); }) Column(){ Text("This is a NodeContainer.") .textAlign(TextAlign.Center).borderRadius(10).backgroundColor(0xFFFFFF) .width('100%').fontSize(16) NodeContainer(this.myNodeController) .borderWidth(1) .width(300) .height(100) } } .padding({ left: 35, right: 35, top: 35, bottom: 35 }) .width("100%") .height("100%") } } ``` ## 通过typeNode创建具体类型的FrameNode节点 通过TypeNode创建具体类型的FrameNode节点,å¯ä»¥æ ¹æ®å±žæ€§è޷喿ޥ壿¥æ£€ç´¢ç”¨æˆ·è®¾ç½®çš„属性信æ¯ã€‚ ```ts import { NodeController, FrameNode, UIContext, BuilderNode, typeNode } from '@kit.ArkUI'; import { BusinessError } from '@kit.BasicServicesKit'; class Params { text: string = ""; constructor(text: string) { this.text = text; } } @Builder function buildText(params: Params) { Column() { Text(params.text) .id("buildText") .border({width:1}) .padding(1) .fontSize(25) .fontWeight(FontWeight.Bold) .margin({ top: 10 }) .visibility(Visibility.Visible) .opacity(0.7) .customProperty("key1", "value1") .width(300) } } const TEST_TAG : string = "FrameNode" class MyNodeController extends NodeController { public frameNode: typeNode.Column | null = null; public uiContext: UIContext | undefined = undefined; private rootNode: FrameNode | null = null; private textNode: BuilderNode<[Params]> | null = null; public textTypeNode: typeNode.Text | null = null; private message: string = "DEFAULT"; makeNode(uiContext: UIContext): FrameNode | null { this.rootNode = new FrameNode(uiContext); this.uiContext = uiContext; this.frameNode = typeNode.createNode(uiContext, "Column"); this.frameNode.attribute .width("100%") .height("100%") this.rootNode.appendChild(this.frameNode); this.textNode = new BuilderNode(uiContext); this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message)); this.frameNode.appendChild(this.textNode.getFrameNode()); this.textTypeNode = typeNode.createNode(uiContext, "Text"); this.textTypeNode.initialize("textTypeNode") .fontSize(25) .visibility(Visibility.Visible) .id("textTypeNode") this.frameNode.appendChild(this.textTypeNode); return this.rootNode; } removeChild(frameNode: FrameNode) { let parent = frameNode.getParent(); if (parent) { parent.removeChild(frameNode); } } getUserConfigBorderWidth(frameNode: FrameNode) { let userConfigBorderWidth = frameNode?.getUserConfigBorderWidth(); // 获å–用户设置的边框宽度 console.log(TEST_TAG + JSON.stringify(userConfigBorderWidth)); } getUserConfigPadding(frameNode: FrameNode) { let userConfigPadding = frameNode?.getUserConfigPadding(); // 获å–ç”¨æˆ·è®¾ç½®çš„å†…è¾¹è· console.log(TEST_TAG + JSON.stringify(userConfigPadding)); } getUserConfigMargin(frameNode: FrameNode) { let userConfigMargin = frameNode?.getUserConfigMargin(); // 获å–ç”¨æˆ·è®¾ç½®çš„å¤–è¾¹è· console.log(TEST_TAG + JSON.stringify(userConfigMargin)); } getUserConfigSize(frameNode: FrameNode) { let userConfigSize = frameNode?.getUserConfigSize(); // 获å–用户设置的宽高 console.log(TEST_TAG + JSON.stringify(userConfigSize)); } getId(frameNode: FrameNode) { let id = frameNode?.getId(); // 获å–用户设置的节点ID console.log(TEST_TAG + id); } getUniqueId(frameNode: FrameNode) { let uniqueId = frameNode?.getUniqueId(); // 获å–系统分é…çš„å”¯ä¸€æ ‡è¯†çš„èŠ‚ç‚¹UniqueID console.log(TEST_TAG + uniqueId); } getNodeType(frameNode: FrameNode) { let nodeType = frameNode?.getNodeType(); // 获å–节点的类型 console.log(TEST_TAG + nodeType); } getOpacity(frameNode: FrameNode) { let opacity = frameNode?.getOpacity(); // 获å–节点的ä¸é€æ˜Žåº¦ console.log(TEST_TAG + JSON.stringify(opacity)); } isVisible(frameNode: FrameNode) { let visible = frameNode?.isVisible(); // 获å–节点是å¦å¯è§ console.log(TEST_TAG + JSON.stringify(visible)); } isClipToFrame(frameNode: FrameNode) { let clipToFrame = frameNode?.isClipToFrame(); // 获å–èŠ‚ç‚¹æ˜¯å¦æ˜¯å‰ªè£åˆ°ç»„件区域 console.log(TEST_TAG + JSON.stringify(clipToFrame)); } isAttached(frameNode: FrameNode) { let attached = frameNode?.isAttached(); // 获å–节点是å¦è¢«æŒ‚è½½åˆ°ä¸»èŠ‚ç‚¹æ ‘ä¸Š console.log(TEST_TAG + JSON.stringify(attached)); } getInspectorInfo(frameNode: FrameNode) { let inspectorInfo = frameNode?.getInspectorInfo(); // 获å–èŠ‚ç‚¹çš„ç»“æž„ä¿¡æ¯ console.log(TEST_TAG + JSON.stringify(inspectorInfo)); } } @Entry @Component struct Index { private myNodeController: MyNodeController = new MyNodeController(); @State index : number = 0; build() { Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { Column(){ Text("This is a NodeContainer.") .textAlign(TextAlign.Center).borderRadius(10).backgroundColor(0xFFFFFF) .width('100%').fontSize(16) NodeContainer(this.myNodeController) .borderWidth(1) .width(300) .height(100) } Button("getUserConfigBorderWidth") .width(300) .onClick(()=>{ const uiContext: UIContext = this.getUIContext(); if (uiContext) { const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; if (node) { this.myNodeController.getUserConfigBorderWidth(node); } } }) Button("getUserConfigPadding") .width(300) .onClick(()=>{ const uiContext: UIContext = this.getUIContext(); if (uiContext) { const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; if (node) { this.myNodeController.getUserConfigPadding(node); } } }) Button("getUserConfigMargin") .width(300) .onClick(()=>{ const uiContext: UIContext = this.getUIContext(); if (uiContext) { const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; if (node) { this.myNodeController.getUserConfigMargin(node); } } }) Button("getUserConfigSize") .width(300) .onClick(()=>{ const uiContext: UIContext = this.getUIContext(); if (uiContext) { const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; if (node) { this.myNodeController.getUserConfigSize(node); } } }) Button("getId") .width(300) .onClick(()=>{ const uiContext: UIContext = this.getUIContext(); if (uiContext) { const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; if (node) { this.myNodeController.getId(node); } } }) Button("getUniqueId") .width(300) .onClick(()=>{ const uiContext: UIContext = this.getUIContext(); if (uiContext) { const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; if (node) { this.myNodeController.getUniqueId(node); } } }) Button("getNodeType") .width(300) .onClick(()=>{ const uiContext: UIContext = this.getUIContext(); if (uiContext) { const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; if (node) { this.myNodeController.getNodeType(node); } } }) Button("getOpacity") .width(300) .onClick(()=>{ const uiContext: UIContext = this.getUIContext(); if (uiContext) { const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; if (node) { this.myNodeController.getOpacity(node); } } }) Button("isVisible") .width(300) .onClick(()=>{ const uiContext: UIContext = this.getUIContext(); if (uiContext) { const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; if (node) { this.myNodeController.isVisible(node); } } }) Button("isClipToFrame") .width(300) .onClick(()=>{ const uiContext: UIContext = this.getUIContext(); if (uiContext) { const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; if (node) { this.myNodeController.isClipToFrame(node); } } }) Button("isAttached") .width(300) .onClick(()=>{ const uiContext: UIContext = this.getUIContext(); if (uiContext) { const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; if (node) { this.myNodeController.isAttached(node); } } }) Button("remove Text") .width(300) .onClick(()=>{ const uiContext: UIContext = this.getUIContext(); if (uiContext) { const node: FrameNode | null = uiContext.getFrameNodeById("textTypeNode") || null; if (node) { this.myNodeController.removeChild(node); this.myNodeController.isAttached(node); } } }) Button("getInspectorInfo") .width(300) .onClick(()=>{ const uiContext: UIContext = this.getUIContext(); if (uiContext) { const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; if (node) { this.myNodeController.getInspectorInfo(node); } } }) Button("getCustomProperty") .width(300) .onClick(()=>{ const uiContext: UIContext = this.getUIContext(); if (uiContext) { const node: FrameNode | null = uiContext.getFrameNodeById("buildText") || null; if (node) { const property = node.getCustomProperty("key1"); console.log(TEST_TAG, JSON.stringify(property)); } } }) } .padding({ left: 35, right: 35, top: 35, bottom: 35 }) .width("100%") .height("100%") } } ``` ## 解除当å‰FrameNode对象对实体FrameNode节点的引用关系 使用[dispose](../reference/apis-arkui/js-apis-arkui-frameNode.md#dispose12)接å£å¯ä»¥ç«‹å³è§£é™¤å½“å‰FrameNode对象对实体FrameNode节点的引用关系。 > **说明:** > > 在调用dispose方法åŽï¼ŒFrameNode对象ä¸å†å¯¹åº”任何实际的FrameNodeèŠ‚ç‚¹ã€‚æ¤æ—¶ï¼Œè‹¥å°è¯•调用以下查询接å£ï¼šgetMeasuredSizeã€getLayoutPositionã€getUserConfigBorderWidthã€getUserConfigPaddingã€getUserConfigMarginã€getUserConfigSize,将导致应用程åºè§¦å‘jscrash。 > > 通过[getUniqueId](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuniqueid12)å¯ä»¥åˆ¤æ–当å‰FrameNode是å¦å¯¹åº”一个实体FrameNode节点。当UniqueId大于0时表示该对象对应一个实体FrameNode节点。 ```ts import { NodeController, FrameNode, BuilderNode } from '@kit.ArkUI'; @Component struct TestComponent { build() { Column() { Text('This is a BuilderNode.') .fontSize(16) .fontWeight(FontWeight.Bold) } .width('100%') .backgroundColor(Color.Gray) } aboutToAppear() { console.error('aboutToAppear'); } aboutToDisappear() { console.error('aboutToDisappear'); } } @Builder function buildComponent() { TestComponent() } class MyNodeController extends NodeController { private rootNode: FrameNode | null = null; private builderNode: BuilderNode<[]> | null = null; makeNode(uiContext: UIContext): FrameNode | null { this.rootNode = new FrameNode(uiContext); this.builderNode = new BuilderNode(uiContext, { selfIdealSize: { width: 200, height: 100 } }); this.builderNode.build(new WrappedBuilder(buildComponent)); const rootRenderNode = this.rootNode.getRenderNode(); if (rootRenderNode !== null) { rootRenderNode.size = { width: 200, height: 200 }; rootRenderNode.backgroundColor = 0xff00ff00; rootRenderNode.appendChild(this.builderNode!.getFrameNode()!.getRenderNode()); } return this.rootNode; } disposeFrameNode() { if (this.rootNode !== null && this.builderNode !== null) { this.rootNode.removeChild(this.builderNode.getFrameNode()); this.builderNode.dispose(); this.rootNode.dispose(); } } removeBuilderNode() { const rootRenderNode = this.rootNode!.getRenderNode(); if (rootRenderNode !== null && this.builderNode !== null && this.builderNode.getFrameNode() !== null) { rootRenderNode.removeChild(this.builderNode!.getFrameNode()!.getRenderNode()); } } } @Entry @Component struct Index { private myNodeController: MyNodeController = new MyNodeController(); build() { Column({ space: 4 }) { NodeContainer(this.myNodeController) Button('FrameNode dispose') .onClick(() => { this.myNodeController.disposeFrameNode(); }) .width('100%') } } } ``` ## FrameNodeçš„æ•°æ®æ‡’åŠ è½½èƒ½åŠ› æä¾›[NodeAdapter](../reference/apis-arkui/js-apis-arkui-frameNode.md#nodeadapter12)对象替代ArkTSä¾§çš„LazyForEach功能,æä¾›è‡ªå®šä¹‰èŠ‚ç‚¹çš„æ•°æ®æ‡’åŠ è½½åŠŸèƒ½ï¼Œå®žçŽ°æŒ‰éœ€è¿ä»£æ•°æ®ã€‚ > **说明:** > > å…¥å‚ä¸èƒ½ä¸ºè´Ÿæ•°ï¼Œå…¥å‚为负数时ä¸åšå¤„ç†ã€‚ ```ts import { FrameNode, NodeController, NodeAdapter, typeNode } from '@kit.ArkUI'; class MyNodeAdapter extends NodeAdapter { uiContext: UIContext cachePool: Array<FrameNode> = new Array(); changed: boolean = false reloadTimes: number = 0; data: Array<string> = new Array(); hostNode?: FrameNode constructor(uiContext: UIContext, count: number) { super(); this.uiContext = uiContext; this.totalNodeCount = count; this.loadData(); } reloadData(count: number): void { this.reloadTimes++; NodeAdapter.attachNodeAdapter(this, this.hostNode); this.totalNodeCount = count; this.loadData(); this.reloadAllItems(); } refreshData(): void { let items = this.getAllAvailableItems() console.log("UINodeAdapter get All items:" + items.length); this.reloadAllItems(); } detachData(): void { NodeAdapter.detachNodeAdapter(this.hostNode); this.reloadTimes = 0; } loadData(): void { for (let i = 0; i < this.totalNodeCount; i++) { this.data[i] = "Adapter ListItem " + i + " r:" + this.reloadTimes; } } changeData(from: number, count: number): void { this.changed = !this.changed; for (let i = 0; i < count; i++) { let index = i + from; this.data[index] = "Adapter ListItem " + (this.changed ? "changed:" : "") + index + " r:" + this.reloadTimes; } this.reloadItem(from, count); } insertData(from: number, count: number): void { for (let i = 0; i < count; i++) { let index = i + from; this.data.splice(index, 0, "Adapter ListItem " + from + "-" + i); } this.insertItem(from, count); this.totalNodeCount += count; console.log("UINodeAdapter after insert count:" + this.totalNodeCount); } removeData(from: number, count: number): void { let arr = this.data.splice(from, count); this.removeItem(from, count); this.totalNodeCount -= arr.length; console.log("UINodeAdapter after remove count:" + this.totalNodeCount); } moveData(from: number, to: number): void { let tmp = this.data.splice(from, 1); this.data.splice(to, 0, tmp[0]); this.moveItem(from, to); } onAttachToNode(target: FrameNode): void { console.log("UINodeAdapter onAttachToNode id:" + target.getUniqueId()); this.hostNode = target; } onDetachFromNode(): void { console.log("UINodeAdapter onDetachFromNode"); } onGetChildId(index: number): number { console.log("UINodeAdapter onGetChildId:" + index); return index; } onCreateChild(index: number): FrameNode { console.log("UINodeAdapter onCreateChild:" + index); if (this.cachePool.length > 0) { let cacheNode = this.cachePool.pop(); if (cacheNode !== undefined) { console.log("UINodeAdapter onCreateChild reused id:" + cacheNode.getUniqueId()); let text = cacheNode?.getFirstChild(); let textNode = text as typeNode.Text; textNode?.initialize(this.data[index]).fontSize(20); return cacheNode; } } console.log("UINodeAdapter onCreateChild createNew"); let itemNode = typeNode.createNode(this.uiContext, "ListItem"); let textNode = typeNode.createNode(this.uiContext, "Text"); textNode.initialize(this.data[index]).fontSize(20); itemNode.appendChild(textNode); return itemNode; } onDisposeChild(id: number, node: FrameNode): void { console.log("UINodeAdapter onDisposeChild:" + id); if (this.cachePool.length < 10) { if (!this.cachePool.includes(node)) { console.log("UINodeAdapter caching node id:" + node.getUniqueId()); this.cachePool.push(node); } } else { node.dispose(); } } onUpdateChild(id: number, node: FrameNode): void { let index = id; let text = node.getFirstChild(); let textNode = text as typeNode.Text; textNode?.initialize(this.data[index]).fontSize(20); } } class MyNodeAdapterController extends NodeController { rootNode: FrameNode | null = null; nodeAdapter: MyNodeAdapter | null = null; makeNode(uiContext: UIContext): FrameNode | null { this.rootNode = new FrameNode(uiContext); let listNode = typeNode.createNode(uiContext, "List"); listNode.initialize({ space: 3 }).borderWidth(2).borderColor(Color.Black); this.rootNode.appendChild(listNode); this.nodeAdapter = new MyNodeAdapter(uiContext, 100); NodeAdapter.attachNodeAdapter(this.nodeAdapter, listNode); return this.rootNode; } } @Entry @Component struct ListNodeTest { adapterController: MyNodeAdapterController = new MyNodeAdapterController(); build() { Column() { Text("ListNode Adapter"); NodeContainer(this.adapterController) .width(300).height(300) .borderWidth(1).borderColor(Color.Black); Row() { Button("Reload") .onClick(() => { this.adapterController.nodeAdapter?.reloadData(50); }) Button("Change") .onClick(() => { this.adapterController.nodeAdapter?.changeData(5, 10) }) Button("Insert") .onClick(() => { this.adapterController.nodeAdapter?.insertData(10, 10); }) } Row() { Button("Remove") .onClick(() => { this.adapterController.nodeAdapter?.removeData(10, 10); }) Button("Move") .onClick(() => { this.adapterController.nodeAdapter?.moveData(2, 5); }) Button("Refresh") .onClick(() => { this.adapterController.nodeAdapter?.refreshData(); }) Button("Detach") .onClick(() => { this.adapterController.nodeAdapter?.detachData(); }) } }.borderWidth(1) .width("100%") } } ```