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 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 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