1# 自定义占位节点 2 3ArkUI提供了系统组件[NodeContainer](../../application-dev/reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md)和[ContentSlot](../../application-dev/reference/apis-arkui/arkui-ts/ts-components-contentSlot.md)作为自定义节点的占位节点。主要用于自定义节点以及自定义节点树的显示。 4 5[NodeContainer](../../application-dev/reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md)作为容器节点存在,具备通用属性,是UI节点。[ContentSlot](../quick-start/arkts-rendering-control-contentslot.md)只是一个语法节点,无通用属性,不参与布局和渲染。支持混合模式开发,当容器是ArkTS组件,子组件在Native侧创建时,推荐使用ContentSlot占位组件。具体使用参考[ContentSlot](../../application-dev/reference/apis-arkui/arkui-ts/ts-components-contentSlot.md)的接口文档说明。 6 7[NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md)是用来占位的系统组件,主要用于自定义节点以及自定义节点树的显示,支持组件的通用属性,对通用属性的处理请参考默认左上角对齐的[Stack](../reference/apis-arkui/arkui-ts/ts-container-stack.md)组件。 8 9[NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md)提供了一系列生命周期回调,通过[makeNode](../reference/apis-arkui/js-apis-arkui-nodeController.md#makenode)回调返回一个 [FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#framenode) 节点树的根节点。将[FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md)节点树挂载到对应的[NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md)下。同时提供了[aboutToAppear](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#abouttoappear)、[aboutToDisappear](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#abouttodisappear)、[aboutToResize](../reference/apis-arkui/js-apis-arkui-nodeController.md#abouttoresize)、[onTouchEvent](../reference/apis-arkui/js-apis-arkui-nodeController.md#ontouchevent)、[rebuild](../reference/apis-arkui/js-apis-arkui-nodeController.md#rebuild)五个回调方法用于监听对应的[NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md)的状态。 10 11每个生命周期的回调的具体含义参考[NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md)的接口文档说明。 12 13> **说明:** 14> 15> - NodeContainer下仅支持挂载自定义的FrameNode节点以及BuilderNode创建的组件树的根节点。 16> 17> - 从API Version 12开始支持的接口,可以通过FrameNode的查询接口返回原生组件的代理节点,代理节点可以作为makeNode的返回值进行返回,但代理节点无法成功挂载在组件树上,最终的显示结果为代理节点挂载失败。 18> 19> - 需要保证一个节点只能作为一个父节点的子节点去使用,否则可能存在显示异常或者功能异常,尤其是页面路由场景或者动效场景。例如,如果通过NodeController将同一个节点挂载在多个NodeContainer上,仅一个占位容器下会显示节点,且多个NodeContainer的可见性、透明度等影响子组件状态的属性更新均会影响被挂载的子节点。 20 21## 使用NodeContainer挂载自定义节点 22 23通过NodeController在NodeContainer下挂载自定义节点。 24 25```ts 26// common.ets 27import { BuilderNode, UIContext } from '@kit.ArkUI' 28 29class Params { 30 text: string = "this is a text" 31} 32 33let buttonNode: BuilderNode<[Params]> | null = null; 34 35@Builder 36function buttonBuilder(params: Params) { 37 Column() { 38 Button(params.text) 39 .fontSize(12) 40 .borderRadius(8) 41 .borderWidth(2) 42 .backgroundColor(Color.Orange) 43 } 44} 45 46export function createNode(uiContext: UIContext) { 47 buttonNode = new BuilderNode<[Params]>(uiContext); 48 buttonNode.build(wrapBuilder(buttonBuilder), { text: "This is a Button" }); 49 return buttonNode; 50} 51 52export function getOrCreateNode(uiContext: UIContext): BuilderNode<[Params]> | null { 53 if (buttonNode?.getFrameNode() && buttonNode?.getFrameNode()?.getUniqueId() != -1) { 54 return buttonNode; 55 } else { 56 return createNode(uiContext); 57 } 58} 59``` 60```ts 61// Index.ets 62import { FrameNode, NodeController, Size, UIContext } from '@kit.ArkUI' 63import { getOrCreateNode } from "./common" 64 65class MyNodeController extends NodeController { 66 private isShow: boolean = false; 67 68 constructor(isShow: boolean) { 69 super(); 70 this.isShow = isShow; 71 } 72 73 makeNode(uiContext: UIContext): FrameNode | null { 74 if (!this.isShow) { 75 return null; 76 } 77 let frameNode = getOrCreateNode(uiContext)?.getFrameNode(); 78 return frameNode ? frameNode : null; 79 } 80 81 aboutToResize(size: Size) { 82 console.log("aboutToResize width : " + size.width + " height : " + size.height) 83 } 84 85 aboutToAppear() { 86 console.log("aboutToAppear") 87 } 88 89 aboutToDisappear() { 90 console.log("aboutToDisappear"); 91 } 92 93 onTouchEvent(event: TouchEvent) { 94 console.log("onTouchEvent"); 95 } 96 97 toShow() { 98 this.isShow = true; 99 this.rebuild(); 100 } 101 102 toHide() { 103 this.isShow = false; 104 this.rebuild(); 105 } 106} 107 108@Entry 109@Component 110struct Index { 111 private myNodeController1: MyNodeController = new MyNodeController(true); 112 private myNodeController2: MyNodeController = new MyNodeController(false); 113 114 build() { 115 Column() { 116 NodeContainer(this.myNodeController1) 117 .width("100%") 118 .height("40%") 119 .backgroundColor(Color.Brown) 120 NodeContainer(this.myNodeController2) 121 .width("100%") 122 .height("40%") 123 .backgroundColor(Color.Gray) 124 Button("Change the place of button") 125 .onClick(() => { 126 // 先在原始占位节点中下树 127 // 后在新的占位节点中上树 128 // 保证自定义节点仅作为一个节点的子节点存在 129 this.myNodeController1.toHide(); 130 this.myNodeController2.toShow(); 131 }) 132 } 133 .padding({ left: 35, right: 35, top: 35 }) 134 .width("100%") 135 .height("100%") 136 } 137} 138``` 139 140## NodeContainer和ContentSlot添加子节点布局差异 141 142[NodeContainer](../../application-dev/reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md)是一个容器节点,布局参考默认左上角对齐的[Stack](../reference/apis-arkui/arkui-ts/ts-container-stack.md)组件,不会按照父容器的布局规则进行布局。[ContentSlot](../../application-dev/reference/apis-arkui/arkui-ts/ts-components-contentSlot.md)只是一个语法节点,不参与布局,添加的子节点会按照父容器的布局规则进行布局。 143 144```ts 145import { FrameNode, NodeContent, NodeController, typeNode, UIContext } from '@kit.ArkUI'; 146 147class NodeContentCtrl { 148 content: NodeContent 149 textNode: Array<typeNode.Text> = new Array(); 150 uiContext: UIContext 151 width: number 152 153 constructor(uiContext: UIContext) { 154 this.content = new NodeContent() 155 this.uiContext = uiContext 156 this.width = Infinity 157 } 158 159 AddNode() { 160 let node = typeNode.createNode(this.uiContext, "Text") 161 node.initialize("ContentText:" + this.textNode.length).fontSize(20) 162 this.textNode.push(node) 163 this.content.addFrameNode(node) 164 } 165 166 RemoveNode() { 167 let node = this.textNode.pop() 168 this.content.removeFrameNode(node) 169 } 170 171 RemoveFront() { 172 let node = this.textNode.shift() 173 this.content.removeFrameNode(node) 174 } 175 176 GetContent(): NodeContent { 177 return this.content 178 } 179} 180 181class MyNodeController extends NodeController { 182 public rootNode: FrameNode | null = null; 183 textNode: Array<typeNode.Text> = new Array(); 184 makeNode(uiContext: UIContext): FrameNode { 185 this.rootNode = new FrameNode(uiContext); 186 return this.rootNode; 187 } 188 189 AddNode(frameNode: FrameNode | null, uiContext: UIContext) { 190 let node = typeNode.createNode(uiContext, "Text") 191 node.initialize("ControllerText:" + this.textNode.length).fontSize(20) 192 this.textNode.push(node) 193 frameNode?.appendChild(node) 194 } 195 196 RemoveNode(frameNode: FrameNode | null) { 197 let node = this.textNode.pop() 198 frameNode?.removeChild(node) 199 } 200 201 RemoveFront(frameNode: FrameNode | null) { 202 let node = this.textNode.shift() 203 frameNode?.removeChild(node) 204 } 205} 206 207@Entry 208@Component 209struct Index { 210 @State message: string = 'Hello World'; 211 controller = new NodeContentCtrl(this.getUIContext()); 212 myNodeController = new MyNodeController(); 213 build() { 214 Row() { 215 Column() { 216 ContentSlot(this.controller.GetContent()) 217 Button("AddToSlot") 218 .onClick(() => { 219 this.controller.AddNode() 220 }) 221 Button("RemoveBack") 222 .onClick(() => { 223 this.controller.RemoveNode() 224 }) 225 Button("RemoveFront") 226 .onClick(() => { 227 this.controller.RemoveFront() 228 }) 229 } 230 .width('50%') 231 Column() { 232 NodeContainer(this.myNodeController) 233 Button("AddToNodeContainer") 234 .onClick(() => { 235 this.myNodeController.AddNode(this.myNodeController.rootNode, this.getUIContext()) 236 }) 237 Button("RemoveBack") 238 .onClick(() => { 239 this.myNodeController.RemoveNode(this.myNodeController.rootNode) 240 }) 241 Button("RemoveFront") 242 .onClick(() => { 243 this.myNodeController.RemoveFront(this.myNodeController.rootNode) 244 }) 245 } 246 .width('50%') 247 } 248 .height('100%') 249 } 250} 251``` 252 253 254