• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Custom Placeholder Nodes
2
3ArkUI provides two types of custom placeholder nodes: the built-in component [NodeContainer](../../application-dev/reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md) and [ContentSlot](../../application-dev/reference/apis-arkui/arkui-ts/ts-components-contentSlot.md). They are used to display custom nodes and custom node trees.
4
5Unlike [NodeContainer](../../application-dev/reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md), which acts as a container node with universal attributes, [ContentSlot](../quick-start/arkts-rendering-control-contentslot.md) is merely a semantic node and does not have universal attributes. It does not engage in layout and rendering processes. For hybrid development scenarios, the **ContentSlot** component is recommended when the container is an ArkTS component and the child component is created on the native side. For details, see [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), a built-in component serving as a placeholder, comes with universal attributes, and its node layout follows the default top-left aligned [Stack](../reference/apis-arkui/arkui-ts/ts-container-stack.md) component.
8
9[NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md) provides a set of lifecycle callbacks, including a [makeNode](../reference/apis-arkui/js-apis-arkui-nodeController.md#makenode) callback that returns the root node of a [FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#framenode) tree. This [FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md) tree is then mounted under the corresponding [NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md). In addition, NodeController provides the following callback methods: [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), and [rebuild](../reference/apis-arkui/js-apis-arkui-nodeController.md#rebuild), which are used to listen for the status of the associated [NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md).
10
11For details about the callbacks, see [NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md).
12
13> **NOTE**
14>
15> - Only custom FrameNodes and root nodes of component trees created by **BuilderNode** are supported under the **NodeContainer**.
16>
17> - 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.
18>
19> - 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.
20
21## Basic concepts
22
23- Custom node: node created using the APIs provided by ArkUI. Custom nodes include custom component nodes (FrameNode), custom render nodes (RenderNode), custom declarative nodes (BuilderNode), and [ComponentContent](../reference/apis-arkui/js-apis-arkui-ComponentContent.md).
24
25- Custom node tree: tree structure where the root node is a custom node.
26
27- Declarative node tree: tree structure where the root node is a declarative node.
28
29- Node tree: data structure that represents the hierarchical relationships between nodes.
30
31- Placeholder node: special node used in a declarative node tree to reserve a spot for a custom node tree. Placeholder nodes include **NodeContainer** and **ContentSlot**. As the main tree of a page is declarative, placeholder nodes are necessary to attach custom nodes built imperatively to the main tree.
32
33## Using NodeContainer to Mount Custom Nodes
34
35You can mount custom nodes under a **NodeContainer** using **NodeController**.
36
37```ts
38// common.ets
39import { BuilderNode, UIContext } from '@kit.ArkUI'
40
41class Params {
42  text: string = "this is a text"
43}
44
45let buttonNode: BuilderNode<[Params]> | null = null;
46
47@Builder
48function buttonBuilder(params: Params) {
49  Column() {
50    Button(params.text)
51      .fontSize(12)
52      .borderRadius(8)
53      .borderWidth(2)
54      .backgroundColor(Color.Orange)
55  }
56}
57
58export function createNode(uiContext: UIContext) {
59  buttonNode = new BuilderNode<[Params]>(uiContext);
60  buttonNode.build(wrapBuilder(buttonBuilder), { text: "This is a Button" });
61  return buttonNode;
62}
63
64export function getOrCreateNode(uiContext: UIContext): BuilderNode<[Params]> | null {
65  if (buttonNode?.getFrameNode() && buttonNode?.getFrameNode()?.getUniqueId() != -1) {
66    return buttonNode;
67  } else {
68    return createNode(uiContext);
69  }
70}
71```
72```ts
73// Index.ets
74import { FrameNode, NodeController, Size, UIContext } from '@kit.ArkUI'
75import { getOrCreateNode } from "./common"
76
77const TEST_TAG: string = "NodeContainer";
78
79class MyNodeController extends NodeController {
80  private isShow: boolean = false;
81
82  constructor(isShow: boolean) {
83    super();
84    this.isShow = isShow;
85  }
86
87  makeNode(uiContext: UIContext): FrameNode | null {
88    if (!this.isShow) {
89      return null;
90    }
91    let frameNode = getOrCreateNode(uiContext)?.getFrameNode();
92    return frameNode ? frameNode : null;
93  }
94
95  aboutToResize(size: Size) {
96    console.log(TEST_TAG + " aboutToResize width : " + size.width + " height : " + size.height)
97  }
98
99  aboutToAppear() {
100    console.log(TEST_TAG + " aboutToAppear")
101  }
102
103  aboutToDisappear() {
104    console.log(TEST_TAG + " aboutToDisappear");
105  }
106
107  onTouchEvent(event: TouchEvent) {
108    console.log(TEST_TAG + " onTouchEvent");
109  }
110
111  toShow() {
112    this.isShow = true;
113    this.rebuild();
114  }
115
116  toHide() {
117    this.isShow = false;
118    this.rebuild();
119  }
120}
121
122@Entry
123@Component
124struct Index {
125  private myNodeController1: MyNodeController = new MyNodeController(true);
126  private myNodeController2: MyNodeController = new MyNodeController(false);
127
128  build() {
129    Column() {
130      NodeContainer(this.myNodeController1)
131        .width("100%")
132        .height("40%")
133        .backgroundColor(Color.Brown)
134      NodeContainer(this.myNodeController2)
135        .width("100%")
136        .height("40%")
137        .backgroundColor(Color.Gray)
138      Button("Change the place of button")
139        .onClick(() => {
140          // First, remove the node from the original placeholder node.
141          // Then, add the node to the new placeholder node.
142          // This ensures that the custom node exists only as the child of one node.
143          this.myNodeController1.toHide();
144          this.myNodeController2.toShow();
145        })
146    }
147    .padding({ left: 35, right: 35, top: 35 })
148    .width("100%")
149    .height("100%")
150  }
151}
152```
153
154## Layout Differences Between Child Nodes Added Using NodeContainer and ContentSlot
155
156[NodeContainer](../../application-dev/reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md) acts as a standard container that manages the layout of its child nodes. The child nodes added using **NodeContainer** follows the layout rules of the default top-left aligned [Stack](../reference/apis-arkui/arkui-ts/ts-container-stack.md) component, instead of those of the parent container. On the other hand, [ContentSlot](../../application-dev/reference/apis-arkui/arkui-ts/ts-components-contentSlot.md) is a semantic node and does not engage in the layout process. Any child nodes added will be arranged according to the layout rules of the parent container.
157
158```ts
159import { FrameNode, NodeContent, NodeController, typeNode, UIContext } from '@kit.ArkUI';
160
161class NodeContentCtrl {
162  content: NodeContent
163  textNode: Array<typeNode.Text> = new Array();
164  uiContext: UIContext
165  width: number
166
167  constructor(uiContext: UIContext) {
168    this.content = new NodeContent()
169    this.uiContext = uiContext
170    this.width = Infinity
171  }
172
173  AddNode() {
174    let node = typeNode.createNode(this.uiContext, "Text")
175    node.initialize("ContentText:" + this.textNode.length).fontSize(20)
176    this.textNode.push(node)
177    this.content.addFrameNode(node)
178  }
179
180  RemoveNode() {
181    let node = this.textNode.pop()
182    this.content.removeFrameNode(node)
183  }
184
185  RemoveFront() {
186    let node = this.textNode.shift()
187    this.content.removeFrameNode(node)
188  }
189
190  GetContent(): NodeContent {
191    return this.content
192  }
193}
194
195class MyNodeController extends NodeController {
196  public rootNode: FrameNode | null = null;
197  textNode: Array<typeNode.Text> = new Array();
198
199  makeNode(uiContext: UIContext): FrameNode {
200    this.rootNode = new FrameNode(uiContext);
201    return this.rootNode;
202  }
203
204  AddNode(frameNode: FrameNode | null, uiContext: UIContext) {
205    let node = typeNode.createNode(uiContext, "Text")
206    node.initialize("ControllerText:" + this.textNode.length).fontSize(20)
207    this.textNode.push(node)
208    frameNode?.appendChild(node)
209  }
210
211  RemoveNode(frameNode: FrameNode | null) {
212    let node = this.textNode.pop()
213    frameNode?.removeChild(node)
214  }
215
216  RemoveFront(frameNode: FrameNode | null) {
217    let node = this.textNode.shift()
218    frameNode?.removeChild(node)
219  }
220}
221
222@Entry
223@Component
224struct Index {
225  @State message: string = 'Hello World';
226  controller = new NodeContentCtrl(this.getUIContext());
227  myNodeController = new MyNodeController();
228
229  build() {
230    Row() {
231      Column() {
232        ContentSlot(this.controller.GetContent())
233        Button("AddToSlot")
234          .onClick(() => {
235            this.controller.AddNode()
236          })
237          .margin(10)
238        Button("RemoveBack")
239          .onClick(() => {
240            this.controller.RemoveNode()
241          })
242          .margin(10)
243        Button("RemoveFront")
244          .onClick(() => {
245            this.controller.RemoveFront()
246          })
247          .margin(10)
248      }
249      .width('50%')
250
251      Column() {
252        NodeContainer(this.myNodeController)
253        Button("AddToNodeContainer")
254          .onClick(() => {
255            this.myNodeController.AddNode(this.myNodeController.rootNode, this.getUIContext())
256          })
257          .margin(10)
258        Button("RemoveBack")
259          .onClick(() => {
260            this.myNodeController.RemoveNode(this.myNodeController.rootNode)
261          })
262          .margin(10)
263        Button("RemoveFront")
264          .onClick(() => {
265            this.myNodeController.RemoveFront(this.myNodeController.rootNode)
266          })
267          .margin(10)
268      }
269      .width('50%')
270    }
271    .height('100%')
272  }
273}
274```
275
276![en-us_image_user-defined-node-01](figures/user-defined-node-01.gif)
277