1# 构建自定义组件
2
3
4ArkUI开发框架在NDK接口提供了自定义UI组件的能力,这些能力包括自定义测算,自定义布局和自定义绘制。开发者通过注册相关自定义回调事件接入ArkUI开发框架的布局渲染流程,这些事件需要使用[registerNodeCustomEvent](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#registernodecustomevent)来进行声明,并通过[addNodeCustomEventReceiver](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#addnodecustomeventreceiver)函数添加组件自定义事件的监听器,在该监听器的回调函数中处理相关自定义测算,自定义布局和自定义绘制逻辑。
5
6
7> **说明:**
8>
9> - 自定义组件事件注册需要[addNodeCustomEventReceiver](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#addnodecustomeventreceiver)声明监听器注册和[registerNodeCustomEvent](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#registernodecustomevent)声明需要的自定义事件类型,监听器只能监听已声明的事件。
10>
11> - 需要关注事件的反注册逻辑,如在组件销毁前调用[removeNodeCustomEventReceiver](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#removenodecustomeventreceiver)移除事件监听器,[unregisterNodeCustomEvent](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#unregisternodecustomevent)通知ArkUI框架已监听的自定义组件事件不再需要监听。
12>
13> - [addNodeCustomEventReceiver](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#addnodecustomeventreceiver)可以添加多个函数指针,每个函数指针都会在对应事件触发时触发,对应的[removeNodeCustomEventReceiver](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#removenodecustomeventreceiver)需要传递对应的函数指针用于移除监听。
14>
15> - [registerNodeCustomEventReceiver](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#registernodecustomeventreceiver)是全局监听函数,不同于[addNodeCustomEventReceiver](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#addnodecustomeventreceiver),[registerNodeCustomEventReceiver](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#registernodecustomeventreceiver)能够监听所有Native组件的自定义事件触发,但只能传递一个函数指针,多次调用使用最后一次的函数指针进行回调,释放时使用unregisterNodeCustomEventReceiver进行反注册。
16>
17> - 自定义组件相关接口([measureNode](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#measurenode)、[layoutNode](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#layoutnode)、[setMeasuredSize](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#setmeasuredsize)、[setLayoutPosition](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#setlayoutposition))仅允许在对应的自定义事件([ARKUI_NODE_CUSTOM_EVENT_ON_MEASURE、ARKUI_NODE_CUSTOM_EVENT_ON_LAYOUT](../reference/apis-arkui/_ark_u_i___native_module.md#arkui_nodecustomeventtype))回调中使用。
18
19
20## 自定义布局容器
21
22以下示例创建了一个自定义容器,该容器将子组件最大值加上额外边距作为自身大小,同时对子组件进行居中排布。
23
24**图1** 自定义容器组件  
25
26![customContainer](figures/customContainer.png)
27
281. 按照[接入ArkTS页面](ndk-access-the-arkts-page.md)创建前置工程。
29
302. 创建自定义容器组件封装对象。
31   ```c
32   // ArkUICustomContainerNode.h
33   // 自定义容器组件示例
34
35   #ifndef MYAPPLICATION_ARKUICUSTOMCONTAINERNODE_H
36   #define MYAPPLICATION_ARKUICUSTOMCONTAINERNODE_H
37
38   #include "ArkUINode.h"
39
40   namespace NativeModule {
41
42   class ArkUICustomContainerNode : public ArkUINode {
43   public:
44       // 使用自定义组件类型ARKUI_NODE_CUSTOM创建组件。
45       ArkUICustomContainerNode()
46           : ArkUINode((NativeModuleInstance::GetInstance()->GetNativeNodeAPI())->createNode(ARKUI_NODE_CUSTOM)) {
47           // 注册自定义事件监听器。
48           nativeModule_->addNodeCustomEventReceiver(handle_, OnStaticCustomEvent);
49           // 声明自定义事件并转递自身作为自定义数据。
50           nativeModule_->registerNodeCustomEvent(handle_, ARKUI_NODE_CUSTOM_EVENT_ON_MEASURE, 0, this);
51           nativeModule_->registerNodeCustomEvent(handle_, ARKUI_NODE_CUSTOM_EVENT_ON_LAYOUT, 0, this);
52       }
53
54       ~ArkUICustomContainerNode() override {
55           // 反注册自定义事件监听器。
56           nativeModule_->removeNodeCustomEventReceiver(handle_, OnStaticCustomEvent);
57           // 取消声明自定义事件。
58           nativeModule_->unregisterNodeCustomEvent(handle_, ARKUI_NODE_CUSTOM_EVENT_ON_MEASURE);
59           nativeModule_->unregisterNodeCustomEvent(handle_, ARKUI_NODE_CUSTOM_EVENT_ON_LAYOUT);
60       }
61
62       void SetPadding(int32_t padding) {
63           padding_ = padding;
64           // 自定义属性事件更新需要主动调用标记脏区接口。
65           nativeModule_->markDirty(handle_, NODE_NEED_MEASURE);
66       }
67
68   private:
69       static void OnStaticCustomEvent(ArkUI_NodeCustomEvent *event) {
70           // 获取组件实例对象,调用相关实例方法。
71           auto customNode = reinterpret_cast<ArkUICustomContainerNode *>(OH_ArkUI_NodeCustomEvent_GetUserData(event));
72           auto type = OH_ArkUI_NodeCustomEvent_GetEventType(event);
73           switch (type) {
74           case ARKUI_NODE_CUSTOM_EVENT_ON_MEASURE:
75               customNode->OnMeasure(event);
76               break;
77           case ARKUI_NODE_CUSTOM_EVENT_ON_LAYOUT:
78               customNode->OnLayout(event);
79               break;
80           default:
81               break;
82           }
83       }
84
85       // 自定义测算逻辑。
86       void OnMeasure(ArkUI_NodeCustomEvent *event) {
87           auto layoutConstrain = OH_ArkUI_NodeCustomEvent_GetLayoutConstraintInMeasure(event);
88           // 创建子节点布局限制,复用父组件布局中的百分比参考值。
89           auto childLayoutConstrain = OH_ArkUI_LayoutConstraint_Copy(layoutConstrain);
90           OH_ArkUI_LayoutConstraint_SetMaxHeight(childLayoutConstrain, 1000);
91           OH_ArkUI_LayoutConstraint_SetMaxWidth(childLayoutConstrain, 1000);
92           OH_ArkUI_LayoutConstraint_SetMinHeight(childLayoutConstrain, 0);
93           OH_ArkUI_LayoutConstraint_SetMinWidth(childLayoutConstrain, 0);
94
95           // 测算子节点获取子节点最大值。
96           auto totalSize = nativeModule_->getTotalChildCount(handle_);
97           int32_t maxWidth = 0;
98           int32_t maxHeight = 0;
99           for (uint32_t i = 0; i < totalSize; i++) {
100               auto child = nativeModule_->getChildAt(handle_, i);
101               // 调用测算接口测算Native组件。
102               nativeModule_->measureNode(child, childLayoutConstrain);
103               auto size = nativeModule_->getMeasuredSize(child);
104               if (size.width > maxWidth) {
105                   maxWidth = size.width;
106               }
107               if (size.height > maxHeight) {
108                   maxHeight = size.height;
109               }
110           }
111           // 自定义测算为所有子节点大小加固定边距。
112           nativeModule_->setMeasuredSize(handle_, maxWidth + 2 * padding_, maxHeight + 2 * padding_);
113       }
114
115       void OnLayout(ArkUI_NodeCustomEvent *event) {
116           // 获取父组件期望位置并设置。
117           auto position = OH_ArkUI_NodeCustomEvent_GetPositionInLayout(event);
118           nativeModule_->setLayoutPosition(handle_, position.x, position.y);
119
120           // 设置子组件居中对齐。
121           auto totalSize = nativeModule_->getTotalChildCount(handle_);
122           auto selfSize = nativeModule_->getMeasuredSize(handle_);
123           for (uint32_t i = 0; i < totalSize; i++) {
124               auto child = nativeModule_->getChildAt(handle_, i);
125               // 获取子组件大小。
126               auto childSize = nativeModule_->getMeasuredSize(child);
127               // 布局子组件位置。
128               nativeModule_->layoutNode(child, (selfSize.width - childSize.width) / 2,
129                                         (selfSize.height - childSize.height) / 2);
130           }
131       }
132
133       int32_t padding_ = 100;
134   };
135
136   } // namespace NativeModule
137
138   #endif // MYAPPLICATION_ARKUICUSTOMCONTAINERNODE_H
139   ```
140
1413. 使用自定义容器创建带文本的示例界面,并沿用[定时器模块相关简单实现](ndk-loading-long-list.md)。
142   ```c
143   // 自定义NDK接口入口。
144
145   #include "NativeEntry.h"
146
147   #include "ArkUICustomContainerNode.h"
148   #include "ArkUITextNode.h"
149
150   #include <arkui/native_node_napi.h>
151   #include <arkui/native_type.h>
152   #include <js_native_api.h>
153
154   namespace NativeModule {
155
156   napi_value CreateNativeRoot(napi_env env, napi_callback_info info) {
157       size_t argc = 1;
158       napi_value args[1] = {nullptr};
159
160       napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
161
162       // 获取NodeContent
163       ArkUI_NodeContentHandle contentHandle;
164       OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle);
165       NativeEntry::GetInstance()->SetContentHandle(contentHandle);
166
167       // 创建自定义容器和文本组件。
168       auto node = std::make_shared<ArkUICustomContainerNode>();
169       node->SetBackgroundColor(0xFFE0FFFF);
170       auto textNode = std::make_shared<ArkUITextNode>();
171       textNode->SetTextContent("CustomContainer Example");
172       textNode->SetFontSize(16);
173       textNode->SetBackgroundColor(0xFFfffacd);
174       textNode->SetTextAlign(ARKUI_TEXT_ALIGNMENT_CENTER);
175       node->AddChild(textNode);
176       CreateNativeTimer(env, textNode.get(), 1, [](void *userData, int32_t count) {
177           auto textNode = reinterpret_cast<ArkUITextNode *>(userData);
178           textNode->SetCircleColor(0xFF00FF7F);
179       });
180
181       // 保持Native侧对象到管理类中,维护生命周期。
182       NativeEntry::GetInstance()->SetRootNode(node);
183       g_env = env;
184       return nullptr;
185   }
186
187   napi_value DestroyNativeRoot(napi_env env, napi_callback_info info) {
188       // 从管理类中释放Native侧对象。
189       NativeEntry::GetInstance()->DisposeRootNode();
190       return nullptr;
191   }
192
193   } // namespace NativeModule
194   ```
195
196
197## 自定义绘制组件
198
199以下示例创建了一个自定义绘制组件,该绘制组件能够绘制自定义矩形,并使用上述自定义容器进行布局排布。
200
201**图2** 自定义绘制组件 
202 
203![customNode](figures/customNode.png)
204
2051. 按照[自定义布局容器](#自定义布局容器)章节准备前置工程。
206
2072. 创建自定义绘制组件封装对象。
208   ```c
209   // ArkUICustomNode.h
210   // 自定义绘制组件示例
211
212   #ifndef MYAPPLICATION_ARKUICUSTOMNODE_H
213   #define MYAPPLICATION_ARKUICUSTOMNODE_H
214
215   #include <native_drawing/drawing_brush.h>
216   #include <native_drawing/drawing_canvas.h>
217   #include <native_drawing/drawing_path.h>
218
219   #include "ArkUINode.h"
220
221   namespace NativeModule {
222
223   class ArkUICustomNode : public ArkUINode {
224   public:
225       // 使用自定义组件类型ARKUI_NODE_CUSTOM创建组件。
226       ArkUICustomNode()
227           : ArkUINode((NativeModuleInstance::GetInstance()->GetNativeNodeAPI())->createNode(ARKUI_NODE_CUSTOM)) {
228           // 注册自定义事件监听器。
229           nativeModule_->addNodeCustomEventReceiver(handle_, OnStaticCustomEvent);
230           // 声明自定义事件并转递自身作为自定义数据。
231           nativeModule_->registerNodeCustomEvent(handle_, ARKUI_NODE_CUSTOM_EVENT_ON_DRAW, 0, this);
232       }
233
234       ~ArkUICustomNode() override {
235           // 反注册自定义事件监听器。
236           nativeModule_->removeNodeCustomEventReceiver(handle_, OnStaticCustomEvent);
237           // 取消声明自定义事件。
238           nativeModule_->unregisterNodeCustomEvent(handle_, ARKUI_NODE_CUSTOM_EVENT_ON_DRAW);
239       }
240
241       void SetRectColor(uint32_t color) {
242           color_ = color;
243           // 自定义绘制属性变更需要主动通知框架。
244           nativeModule_->markDirty(handle_, NODE_NEED_RENDER);
245       }
246
247   private:
248       static void OnStaticCustomEvent(ArkUI_NodeCustomEvent *event) {
249           // 获取组件实例对象,调用相关实例方法。
250           auto customNode = reinterpret_cast<ArkUICustomNode *>(OH_ArkUI_NodeCustomEvent_GetUserData(event));
251           auto type = OH_ArkUI_NodeCustomEvent_GetEventType(event);
252           switch (type) {
253           case ARKUI_NODE_CUSTOM_EVENT_ON_DRAW:
254               customNode->OnDraw(event);
255               break;
256           default:
257               break;
258           }
259       }
260
261       // 自定义绘制逻辑。
262       void OnDraw(ArkUI_NodeCustomEvent *event) {
263           auto drawContext = OH_ArkUI_NodeCustomEvent_GetDrawContextInDraw(event);
264           // 获取图形绘制对象。
265           auto drawCanvas = reinterpret_cast<OH_Drawing_Canvas *>(OH_ArkUI_DrawContext_GetCanvas(drawContext));
266           // 获取组件大小。
267           auto size = OH_ArkUI_DrawContext_GetSize(drawContext);
268           // 绘制自定义内容。
269           auto path = OH_Drawing_PathCreate();
270           OH_Drawing_PathMoveTo(path, size.width / 4, size.height / 4);
271           OH_Drawing_PathLineTo(path, size.width * 3 / 4, size.height / 4);
272           OH_Drawing_PathLineTo(path, size.width * 3 / 4, size.height * 3 / 4);
273           OH_Drawing_PathLineTo(path, size.width / 4, size.height * 3 / 4);
274           OH_Drawing_PathLineTo(path, size.width / 4, size.height / 4);
275           OH_Drawing_PathClose(path);
276           auto brush = OH_Drawing_BrushCreate();
277           OH_Drawing_BrushSetColor(brush, color_);
278           OH_Drawing_CanvasAttachBrush(drawCanvas, brush);
279           OH_Drawing_CanvasDrawPath(drawCanvas, path);
280           // 释放资源
281           OH_Drawing_BrushDestroy(brush);
282           OH_Drawing_PathDestroy(path);
283       }
284
285       uint32_t color_ = 0xFFFFE4B5;
286   };
287
288   } // namespace NativeModule
289
290   #endif // MYAPPLICATION_ARKUICUSTOMNODE_H
291   ```
292
2933. 使用自定义绘制组件和自定义容器创建示例界面,并沿用[定时器模块相关简单实现](ndk-loading-long-list.md)。
294   ```c
295   // 自定义NDK接口入口组件。
296
297   #include "NativeEntry.h"
298
299   #include "ArkUICustomContainerNode.h"
300   #include "ArkUICustomNode.h"
301
302   #include <arkui/native_node_napi.h>
303   #include <arkui/native_type.h>
304   #include <js_native_api.h>
305
306   namespace NativeModule {
307
308   napi_value CreateNativeRoot(napi_env env, napi_callback_info info) {
309       size_t argc = 1;
310       napi_value args[1] = {nullptr};
311
312       napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
313
314       // 获取NodeContent
315       ArkUI_NodeContentHandle contentHandle;
316       OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle);
317       NativeEntry::GetInstance()->SetContentHandle(contentHandle);
318
319       // 创建自定义容器和自定义绘制组件。
320       auto node = std::make_shared<ArkUICustomContainerNode>();
321       node->SetBackgroundColor(0xFFE0FFFF);
322       auto customNode = std::make_shared<ArkUICustomNode>();
323       customNode->SetBackgroundColor(0xFFD3D3D3);
324       customNode->SetWidth(150);
325       customNode->SetHeight(150);
326       node->AddChild(customNode);
327       CreateNativeTimer(env, customNode.get(), 1, [](void *userData, int32_t count) {
328           auto customNode = reinterpret_cast<ArkUICustomNode *>(userData);
329           customNode->SetRectColor(0xFF00FF7F);
330       });
331
332       // 保持Native侧对象到管理类中,维护生命周期。
333       NativeEntry::GetInstance()->SetRootNode(node);
334       g_env = env;
335       return nullptr;
336   }
337
338   napi_value DestroyNativeRoot(napi_env env, napi_callback_info info) {
339       // 从管理类中释放Native侧对象。
340       NativeEntry::GetInstance()->DisposeRootNode();
341       return nullptr;
342   }
343
344   } // namespace NativeModule
345
346   ```
347