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