1e41f4b71Sopenharmony_ci# Custom Placeholder Node 2e41f4b71Sopenharmony_ci 3e41f4b71Sopenharmony_ciArkUI provides ArkTS built-in components as placeholder nodes for custom nodes. These placeholder nodes have universal component attributes. 4e41f4b71Sopenharmony_ci 5e41f4b71Sopenharmony_ci## NodeContainer and NodeController 6e41f4b71Sopenharmony_ci 7e41f4b71Sopenharmony_ci[NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md), as a built-in component, only has universal component attributes, and its node layout follows the default top-left aligned [Stack](../reference/apis-arkui/arkui-ts/ts-container-stack.md) component. As a placeholder container, **NodeContainer** is primarily used for displaying custom nodes and for the display and reuse of custom node trees. 8e41f4b71Sopenharmony_ci 9e41f4b71Sopenharmony_ci[NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md) provides a set of lifecycle callbacks, including a **makeNode** callback that returns the root node of a **FrameNode** tree. This [FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md) tree is then mounted under the corresponding **NodeContainer**. In addition, **NodeController** provides the following callback methods: **aboutToAppear**, **aboutToDisappear**, **aboutToResize**, **onTouchEvent**, and **rebuild**, which are used to listen for the status of the associated **NodeContainer**. 10e41f4b71Sopenharmony_ci 11e41f4b71Sopenharmony_ciFor details about the callbacks, see [NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md). 12e41f4b71Sopenharmony_ci 13e41f4b71Sopenharmony_ci> **NOTE** 14e41f4b71Sopenharmony_ci> 15e41f4b71Sopenharmony_ci> - Only custom FrameNodes and root nodes of component trees created by **BuilderNode** are supported under the **NodeContainer**. 16e41f4b71Sopenharmony_ci> 17e41f4b71Sopenharmony_ci> - Since API version 12, you can obtain a built-in component's proxy node through the query API of the FrameNode. This proxy node can be returned as the result of the **makeNode** callback, but it cannot be successfully mounted on the component tree, resulting in a failed display of the proxy node. 18e41f4b71Sopenharmony_ci> 19e41f4b71Sopenharmony_ci> - A node must be used as the child of only one parent node to avoid display or functional issues, particularly in page routing and animation scenarios. For example, if a single node is mounted on multiple **NodeContainer**s through **NodeController**, only one of the **NodeContainer**s will display the node. In addition, any updates to attributes such as visibility and opacity in any of these **NodeContainer**s, which can affect the child component state, will all influence the mounted child node. 20e41f4b71Sopenharmony_ci 21e41f4b71Sopenharmony_ci```ts 22e41f4b71Sopenharmony_ciimport { BuilderNode, FrameNode, NodeController, Size, UIContext } from '@kit.ArkUI' 23e41f4b71Sopenharmony_ci 24e41f4b71Sopenharmony_ciclass Params { 25e41f4b71Sopenharmony_ci text: string = "this is a text" 26e41f4b71Sopenharmony_ci} 27e41f4b71Sopenharmony_ci 28e41f4b71Sopenharmony_ci@Builder 29e41f4b71Sopenharmony_cifunction buttonBuilder(params: Params) { 30e41f4b71Sopenharmony_ci Column() { 31e41f4b71Sopenharmony_ci Button(params.text) 32e41f4b71Sopenharmony_ci .fontSize(12) 33e41f4b71Sopenharmony_ci .borderRadius(8) 34e41f4b71Sopenharmony_ci .borderWidth(2) 35e41f4b71Sopenharmony_ci .backgroundColor(Color.Orange) 36e41f4b71Sopenharmony_ci } 37e41f4b71Sopenharmony_ci} 38e41f4b71Sopenharmony_ci 39e41f4b71Sopenharmony_cilet buttonNode: BuilderNode<[Params]> | null = null; 40e41f4b71Sopenharmony_ci 41e41f4b71Sopenharmony_ciclass MyNodeController extends NodeController { 42e41f4b71Sopenharmony_ci private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(buttonBuilder); 43e41f4b71Sopenharmony_ci private isShow: boolean = false; 44e41f4b71Sopenharmony_ci 45e41f4b71Sopenharmony_ci constructor(isShow: boolean) { 46e41f4b71Sopenharmony_ci super(); 47e41f4b71Sopenharmony_ci this.isShow = isShow; 48e41f4b71Sopenharmony_ci } 49e41f4b71Sopenharmony_ci 50e41f4b71Sopenharmony_ci makeNode(uiContext: UIContext): FrameNode | null { 51e41f4b71Sopenharmony_ci if (!this.isShow) { 52e41f4b71Sopenharmony_ci return null; 53e41f4b71Sopenharmony_ci } 54e41f4b71Sopenharmony_ci if (buttonNode == null) { 55e41f4b71Sopenharmony_ci buttonNode = new BuilderNode<[Params]>(uiContext); 56e41f4b71Sopenharmony_ci buttonNode.build(this.wrapBuilder, { text: "This is a Button" }) 57e41f4b71Sopenharmony_ci } 58e41f4b71Sopenharmony_ci let frameNode = buttonNode?.getFrameNode(); 59e41f4b71Sopenharmony_ci return frameNode ? frameNode : null; 60e41f4b71Sopenharmony_ci } 61e41f4b71Sopenharmony_ci 62e41f4b71Sopenharmony_ci aboutToResize(size: Size) { 63e41f4b71Sopenharmony_ci console.log("aboutToResize width : " + size.width + " height : " + size.height) 64e41f4b71Sopenharmony_ci } 65e41f4b71Sopenharmony_ci 66e41f4b71Sopenharmony_ci aboutToAppear() { 67e41f4b71Sopenharmony_ci console.log("aboutToAppear") 68e41f4b71Sopenharmony_ci } 69e41f4b71Sopenharmony_ci 70e41f4b71Sopenharmony_ci aboutToDisappear() { 71e41f4b71Sopenharmony_ci console.log("aboutToDisappear"); 72e41f4b71Sopenharmony_ci } 73e41f4b71Sopenharmony_ci 74e41f4b71Sopenharmony_ci onTouchEvent(event: TouchEvent) { 75e41f4b71Sopenharmony_ci console.log("onTouchEvent"); 76e41f4b71Sopenharmony_ci } 77e41f4b71Sopenharmony_ci 78e41f4b71Sopenharmony_ci toShow() { 79e41f4b71Sopenharmony_ci this.isShow = true; 80e41f4b71Sopenharmony_ci this.rebuild(); 81e41f4b71Sopenharmony_ci } 82e41f4b71Sopenharmony_ci 83e41f4b71Sopenharmony_ci toHide() { 84e41f4b71Sopenharmony_ci this.isShow = false; 85e41f4b71Sopenharmony_ci this.rebuild(); 86e41f4b71Sopenharmony_ci } 87e41f4b71Sopenharmony_ci} 88e41f4b71Sopenharmony_ci 89e41f4b71Sopenharmony_ci@Entry 90e41f4b71Sopenharmony_ci@Component 91e41f4b71Sopenharmony_cistruct Index { 92e41f4b71Sopenharmony_ci private myNodeController1: MyNodeController = new MyNodeController(true); 93e41f4b71Sopenharmony_ci private myNodeController2: MyNodeController = new MyNodeController(false); 94e41f4b71Sopenharmony_ci 95e41f4b71Sopenharmony_ci build() { 96e41f4b71Sopenharmony_ci Column() { 97e41f4b71Sopenharmony_ci NodeContainer(this.myNodeController1) 98e41f4b71Sopenharmony_ci .width("100%") 99e41f4b71Sopenharmony_ci .height("40%") 100e41f4b71Sopenharmony_ci .backgroundColor(Color.Brown) 101e41f4b71Sopenharmony_ci NodeContainer(this.myNodeController2) 102e41f4b71Sopenharmony_ci .width("100%") 103e41f4b71Sopenharmony_ci .height("40%") 104e41f4b71Sopenharmony_ci .backgroundColor(Color.Gray) 105e41f4b71Sopenharmony_ci Button("Change the place of button") 106e41f4b71Sopenharmony_ci .onClick(() => { 107e41f4b71Sopenharmony_ci // First, remove the node from the original placeholder node. 108e41f4b71Sopenharmony_ci // Then, add the node to the new placeholder node. 109e41f4b71Sopenharmony_ci // Ensure that the custom node only exists as the child of one node. 110e41f4b71Sopenharmony_ci this.myNodeController1.toHide(); 111e41f4b71Sopenharmony_ci this.myNodeController2.toShow(); 112e41f4b71Sopenharmony_ci }) 113e41f4b71Sopenharmony_ci } 114e41f4b71Sopenharmony_ci .padding({ left: 35, right: 35, top: 35 }) 115e41f4b71Sopenharmony_ci .width("100%") 116e41f4b71Sopenharmony_ci .height("100%") 117e41f4b71Sopenharmony_ci } 118e41f4b71Sopenharmony_ci} 119e41f4b71Sopenharmony_ci``` 120