• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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](../ui/state-management/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](../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## 基本概念
22
23- 自定义节点:使用ArkUI提供的接口,以命令式创建的节点。包括自定义组件节点(FrameNode)、自定义渲染节点(RenderNode)、自定义声明式节点(BuilderNode)、[ComponentContent](../reference/apis-arkui/js-apis-arkui-ComponentContent.md)等。
24
25- 自定义节点树:根节点为自定义节点的节点树。
26
27- 声明式节点树:根节点为声明式节点的节点树。
28
29- 节点树:一种常见的数据结构,用于表示节点的层级关系。
30
31- 占位节点:用于在声明式节点树上为自定义节点树预留位置的节点,主要包括NodeContainer和ContentSlot。鉴于页面的主树采用声明式节点树,因此,唯有借助占位节点,才能将命令式构建的自定义节点成功挂载至声明式节点树上。
32
33## 使用NodeContainer挂载自定义节点
34
35通过NodeController在NodeContainer下挂载自定义节点。
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          // 先在原始占位节点中下树
141          // 后在新的占位节点中上树
142          // 保证自定义节点仅作为一个节点的子节点存在
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## NodeContainer和ContentSlot添加子节点布局差异
155
156[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)只是一个语法节点,不参与布局,添加的子节点会按照父容器的布局规则进行布局。
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![zh-cn_image_user-defined-node-01](figures/user-defined-node-01.gif)
277