1# Building Custom Components
2
3
4The ArkUI development framework provides capabilities for creating custom UI components through NDK APIs, including custom measurement, layout, and drawing. You can integrate into ArkUI's layout and rendering process by registering custom callback events using the [registerNodeCustomEvent](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#registernodecustomevent) function and adding custom event listeners for components with the [addNodeCustomEventReceiver](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#addnodecustomeventreceiver) function. The logic for custom measurement, layout, and drawing is handled within the callback functions of these listeners.
5
6
7> **NOTE**
8>
9> - Custom component event registration requires [addNodeCustomEventReceiver](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#addnodecustomeventreceiver) to declare the listener registration and registerNodeCustomEvent](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#registernodecustomevent) to declare the required custom event types; listeners can only listen to declared events.
10>
11> - IPay attention to the logic of event deregistration, such as calling [removeNodeCustomEventReceiver](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#removenodecustomeventreceiver) to remove the event listener before the component is destroyed, and [unregisterNodeCustomEvent](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#unregisternodecustomevent) to notify the ArkUI framework that the custom component events that have been listened to are no longer needed.
12>
13> - [addNodeCustomEventReceiver](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#addnodecustomeventreceiver) can add multiple function pointers, each of which is triggered when the corresponding event occurs. To remove a listener, the corresponding [removeNodeCustomEventReceiver](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#removenodecustomeventreceiver) function must be called with the exact function pointer used for adding the listener.
14>
15> - [registerNodeCustomEventReceiver](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#registernodecustomeventreceiver) is a global event listener function. Unlike [addNodeCustomEventReceiver](../reference/apis-arkui/_ark_u_i___native_node_a_p_i__1.md#addnodecustomeventreceiver), **registerNodeCustomEventReceiver** can listen for the event triggers of all native components, but it can only accept a single function pointer. If it is called multiple times, only the last function pointer provided will be used for callbacks. To release the listener, use the **unregisterNodeCustomEventReceiver** function.
16>
17> - Custom component-related APIs ([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)) can only be used in the corresponding custom event callbacks ([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## Custom Layout Container
21
22The following example creates a custom container that uses the maximum size of its child components, plus additional padding, as its own size, while center-aligning the child components.
23
24**Figure 1** Custom container component
25
26![customContainer](figures/customContainer.png)
27
281. Follow the instructions in [Integrating with ArkTS Pages](ndk-access-the-arkts-page.md) to create a project.
29
302. Create an encapsulated object for the custom container component.
31   ```c
32   // ArkUICustomContainerNode.h
33   // Example of a custom container component
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       // Create the component using the custom component type ARKUI_NODE_CUSTOM.
45       ArkUICustomContainerNode()
46           : ArkUINode((NativeModuleInstance::GetInstance()->GetNativeNodeAPI())->createNode(ARKUI_NODE_CUSTOM)) {
47           // Register the custom event listener.
48           nativeModule_->addNodeCustomEventReceiver(handle_, OnStaticCustomEvent);
49           // Declare the custom event and pass itself as custom data.
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           // Deregister the custom event listener.
56           nativeModule_->removeNodeCustomEventReceiver(handle_, OnStaticCustomEvent);
57           // Undeclare the custom event.
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           // To update a custom property event, you need to proactively call the API for marking the dirty region.
65           nativeModule_->markDirty(handle_, NODE_NEED_MEASURE);
66       }
67
68   private:
69       static void OnStaticCustomEvent(ArkUI_NodeCustomEvent *event) {
70           // Obtain the component instance object and call the related instance method.
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       // Custom measurement logic.
86       void OnMeasure(ArkUI_NodeCustomEvent *event) {
87           auto layoutConstrain = OH_ArkUI_NodeCustomEvent_GetLayoutConstraintInMeasure(event);
88           // Create child node layout constraints, reusing the percentage reference values in the parent component layout.
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           // Measure the child nodes to get the maximum values.
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               // Call the measurement API to measure the native component.
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           // Custom measurement is the sum of all child node sizes plus a fixed margin.
112           nativeModule_->setMeasuredSize(handle_, maxWidth + 2 * padding_, maxHeight + 2 * padding_);
113       }
114
115       void OnLayout(ArkUI_NodeCustomEvent *event) {
116           // Obtain the desired position of the parent component and set it.
117           auto position = OH_ArkUI_NodeCustomEvent_GetPositionInLayout(event);
118           nativeModule_->setLayoutPosition(handle_, position.x, position.y);
119
120           // Set child components to be center-aligned.
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               // Obtain the child component size.
126               auto childSize = nativeModule_->getMeasuredSize(child);
127               // Lay out the child components.
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. Use the custom container to create a sample UI with text, and continue with the [timer module simple implementation](ndk-loading-long-list.md).
142   ```c
143   // Custom NDK API entry point.
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       // Obtain NodeContent.
163       ArkUI_NodeContentHandle contentHandle;
164       OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle);
165       NativeEntry::GetInstance()->SetContentHandle(contentHandle);
166
167       // Create a custom container and text component.
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       // Keep the native side object in the management class to maintain its lifecycle.
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       // Release the native side object from the management class.
189       NativeEntry::GetInstance()->DisposeRootNode();
190       return nullptr;
191   }
192
193   } // namespace NativeModule
194   ```
195
196
197## Custom Drawing Component
198
199The following example creates a custom drawing component that can draw a custom rectangle and uses the aforementioned custom container for layout arrangement.
200
201**Figure 2** Custom drawing component
202
203![customNode](figures/customNode.png)
204
2051. Prepare a project as instructed in [Custom Layout Container](#custom-layout-container).
206
2072. Create an encapsulated object for the custom drawing component.
208   ```c
209   // ArkUICustomNode.h
210   // Example of a custom drawing component
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       // Create the component using the custom component type ARKUI_NODE_CUSTOM.
226       ArkUICustomNode()
227           : ArkUINode((NativeModuleInstance::GetInstance()->GetNativeNodeAPI())->createNode(ARKUI_NODE_CUSTOM)) {
228           // Register the custom event listener.
229           nativeModule_->addNodeCustomEventReceiver(handle_, OnStaticCustomEvent);
230           // Declare the custom event and pass itself as custom data.
231           nativeModule_->registerNodeCustomEvent(handle_, ARKUI_NODE_CUSTOM_EVENT_ON_DRAW, 0, this);
232       }
233
234       ~ArkUICustomNode() override {
235           // Deregister the custom event listener.
236           nativeModule_->removeNodeCustomEventReceiver(handle_, OnStaticCustomEvent);
237           // Undeclare the custom event.
238           nativeModule_->unregisterNodeCustomEvent(handle_, ARKUI_NODE_CUSTOM_EVENT_ON_DRAW);
239       }
240
241       void SetRectColor(uint32_t color) {
242           color_ = color;
243           // Custom drawing property changes require proactive notification to the framework.
244           nativeModule_->markDirty(handle_, NODE_NEED_RENDER);
245       }
246
247   private:
248       static void OnStaticCustomEvent(ArkUI_NodeCustomEvent *event) {
249           // Obtain the component instance object and call the related instance method.
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       // Custom drawing logic
262       void OnDraw(ArkUI_NodeCustomEvent *event) {
263           auto drawContext = OH_ArkUI_NodeCustomEvent_GetDrawContextInDraw(event);
264           // Obtain the graphics drawing object.
265           auto drawCanvas = reinterpret_cast<OH_Drawing_Canvas *>(OH_ArkUI_DrawContext_GetCanvas(drawContext));
266           // Obtain the component size.
267           auto size = OH_ArkUI_DrawContext_GetSize(drawContext);
268           // Draw custom content.
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           // Release resources.
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. Create a sample UI using the custom drawing component and the custom container, and continue with the [timer module simple implementation](ndk-loading-long-list.md).
294   ```c
295   // Custom NDK API entry point.
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       // Obtain NodeContent.
315       ArkUI_NodeContentHandle contentHandle;
316       OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle);
317       NativeEntry::GetInstance()->SetContentHandle(contentHandle);
318
319       // Create a custom container and a custom drawing component.
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       // Keep the native side object in the management class to maintain its lifecycle.
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       // Release the native side object from the management class.
340       NativeEntry::GetInstance()->DisposeRootNode();
341       return nullptr;
342   }
343
344   } // namespace NativeModule
345
346   ```
347