Custom Drawing
The NDK supports custom drawing nodes, enabling you to implement custom rendering logic using the NDK APIs.
Custom Drawing Content
When the registered event is detected as a drawing type, you can use the custom drawing feature to implement your own drawing logic and custom content.
NOTE
During event registration, you must register the event as a drawing event (for example, ARKUI_NODE_CUSTOM_EVENT_ON_DRAW). You can find the event types and their meanings by referring to the ArkUI_NodeCustomEventType enum.
To implement custom drawing logic, you must define custom UserData and pass it during event registration.
The following scenarios are based on the project configuration described in Integrating with ArkTS Pages.
-
Create a custom node by passing the ARKUI_NODE_CUSTOM enumerated value through the createNode API of ArkUI_NativeNodeAPI_1.
auto customNode = nodeAPI->createNode(ARKUI_NODE_CUSTOM); -
Register the custom event with the custom node, event type (for example, ARKUI_NODE_CUSTOM_EVENT_ON_FOREGROUND_DRAW; for details about the supported event types, see ArkUI_NodeCustomEventType), event ID, and UserData.
// UserData struct A { int32_t a = 6; bool flag = true; ArkUI_NodeHandle node; }; A *a = new A; a->node = customNode; // ... nodeAPI->registerNodeCustomEvent(customNode, ARKUI_NODE_CUSTOM_EVENT_ON_FOREGROUND_DRAW, 1, a); // Define the event callback. nodeAPI->registerNodeCustomEventReceiver([](ArkUI_NodeCustomEvent *event) { // Event callback logic // ... }); -
In the callback, obtain the event type, event ID, and UserData to determine the execution logic using the following APIs: OH_ArkUI_NodeCustomEvent_GetEventType, OH_ArkUI_NodeCustomEvent_GetEventTargetId, and OH_ArkUI_NodeCustomEvent_GetUserData.
auto type = OH_ArkUI_NodeCustomEvent_GetEventType(event); auto targetId = OH_ArkUI_NodeCustomEvent_GetEventTargetId(event); auto userData = reinterpret_cast<A *>(OH_ArkUI_NodeCustomEvent_GetUserData(event)); -
OH_ArkUI_NodeCustomEvent_GetDrawContextInDraw obtains the drawing context through the custom component event and transfers the drawing context to OH_ArkUI_DrawContext_GetCanvas to obtain the canvas pointer for drawing. The pointer is then converted to a OH_Drawing_Canvas pointer for drawing.
// Obtain the drawing context for the custom event. auto *drawContext = OH_ArkUI_NodeCustomEvent_GetDrawContextInDraw(event); // Obtain the drawing canvas pointer. auto *canvas1 = OH_ArkUI_DrawContext_GetCanvas(drawContext); // Cast the pointer to an OH_Drawing_Canvas pointer for drawing. OH_Drawing_Canvas *canvas = reinterpret_cast<OH_Drawing_Canvas *>(canvas1); // Drawing logic. int32_t width = SIZE_1000; // SIZE_1000 = 1000 int32_t height = SIZE_1000; // SIZE_1000 = 1000 auto path = OH_Drawing_PathCreate(); OH_Drawing_PathMoveTo(path, width / SIZE_4, height / SIZE_4); // SIZE_4 = 4 OH_Drawing_PathLineTo(path, width * SIZE_3 / SIZE_4, height * SIZE_3 / SIZE_4); // SIZE_3 = 3,SIZE_4 = 4 OH_Drawing_PathClose(path); auto pen = OH_Drawing_PenCreate(); OH_Drawing_PenSetWidth(pen, SIZE_10); // SIZE_10=10 OH_Drawing_PenSetColor(pen, OH_Drawing_ColorSetArgb(0xFF, 0x00, 0x4A, 0x4F)); OH_Drawing_CanvasAttachPen(canvas, pen); OH_Drawing_CanvasDrawPath(canvas, path);
Complete content drawing example:

Custom Drawing Foreground and Background
The following example creates a custom drawing component capable of rendering custom rectangles, customizing foreground and background elements, and using the custom layout container for layout management.
-
Prepare a project as instructed in Custom Layout Container.
-
Create an encapsulated object for the custom drawing component.
#ifndef MYAPPLICATION_ARKUICUSTOMNODE_H #define MYAPPLICATION_ARKUICUSTOMNODE_H #include <native_drawing/drawing_brush.h> #include <native_drawing/drawing_canvas.h> #include <native_drawing/drawing_path.h> #include "ArkUINode.h" namespace NativeModule { class ArkUICustomNode : public ArkUINode { public: // Create the component using the custom component type ARKUI_NODE_CUSTOM. ArkUICustomNode() : ArkUINode((NativeModuleInstance::GetInstance()->GetNativeNodeAPI())->createNode(ARKUI_NODE_CUSTOM)) { // Register the custom event listener. nativeModule_->addNodeCustomEventReceiver(handle_, OnStaticCustomEvent); // Declare the custom event and pass itself as custom data. nativeModule_->registerNodeCustomEvent(handle_, ARKUI_NODE_CUSTOM_EVENT_ON_DRAW_FRONT, 0, this); nativeModule_->registerNodeCustomEvent(handle_, ARKUI_NODE_CUSTOM_EVENT_ON_DRAW, 0, this); nativeModule_->registerNodeCustomEvent(handle_, ARKUI_NODE_CUSTOM_EVENT_ON_DRAW_BEHIND, 0, this); // Register a callback for the drawing completion event. OH_ArkUI_RegisterDrawCallbackOnNodeHandle(handle_, nullptr, [](void* userData) {}); } ~ArkUICustomNode() override { // Unregister the custom event listener. nativeModule_->removeNodeCustomEventReceiver(handle_, OnStaticCustomEvent); // Remove the declaration of the custom event. nativeModule_->unregisterNodeCustomEvent(handle_, ARKUI_NODE_CUSTOM_EVENT_ON_DRAW_FRONT); nativeModule_->unregisterNodeCustomEvent(handle_, ARKUI_NODE_CUSTOM_EVENT_ON_DRAW); nativeModule_->unregisterNodeCustomEvent(handle_, ARKUI_NODE_CUSTOM_EVENT_ON_DRAW_BEHIND); OH_ArkUI_UnregisterDrawCallbackOnNodeHandle(handle_); } private: int32_t NUM_2 = 2; int32_t NUM_3 = 3; int32_t NUM_4 = 4; int32_t NUM_5 = 5; static void OnStaticCustomEvent(ArkUI_NodeCustomEvent *event) { // Obtain the component instance object and call the related instance method. // ... auto customNode = reinterpret_cast<ArkUICustomNode *>(OH_ArkUI_NodeCustomEvent_GetUserData(event)); auto type = OH_ArkUI_NodeCustomEvent_GetEventType(event); switch (type) { // The drawing layer is from low to high. case ARKUI_NODE_CUSTOM_EVENT_ON_DRAW_BEHIND: customNode->OnDrawBehind(event); break; case ARKUI_NODE_CUSTOM_EVENT_ON_DRAW: customNode->OnDraw(event); break; case ARKUI_NODE_CUSTOM_EVENT_ON_DRAW_FRONT: customNode->OnDrawFront(event); break; // ... default: break; } } // Custom drawing logic void OnDrawBehind(ArkUI_NodeCustomEvent *event) { auto drawContext = OH_ArkUI_NodeCustomEvent_GetDrawContextInDraw(event); // Obtain the graphics drawing object. auto drawCanvas = reinterpret_cast<OH_Drawing_Canvas *>(OH_ArkUI_DrawContext_GetCanvas(drawContext)); // Obtain the component size. auto size = OH_ArkUI_DrawContext_GetSize(drawContext); // Draw custom content. auto path = OH_Drawing_PathCreate(); OH_Drawing_PathMoveTo(path, size.width / NUM_5, size.height / NUM_5); OH_Drawing_PathLineTo(path, size.width * NUM_4 / NUM_5, size.height / NUM_5); OH_Drawing_PathLineTo(path, size.width * NUM_4 / NUM_5, size.height * NUM_4 / NUM_5); OH_Drawing_PathLineTo(path, size.width / NUM_5, size.height * NUM_4 / NUM_5); OH_Drawing_PathLineTo(path, size.width / NUM_5, size.height / NUM_5); OH_Drawing_PathClose(path); auto brush = OH_Drawing_BrushCreate(); OH_Drawing_BrushSetColor(brush, 0xFFF0FAFF); // Pale blue OH_Drawing_CanvasAttachBrush(drawCanvas, brush); OH_Drawing_CanvasDrawPath(drawCanvas, path); // Release resources. OH_Drawing_BrushDestroy(brush); OH_Drawing_PathDestroy(path); } void OnDraw(ArkUI_NodeCustomEvent *event) { auto drawContext = OH_ArkUI_NodeCustomEvent_GetDrawContextInDraw(event); // Obtain the graphics drawing object. auto drawCanvas = reinterpret_cast<OH_Drawing_Canvas *>(OH_ArkUI_DrawContext_GetCanvas(drawContext)); // Obtain the component size. auto size = OH_ArkUI_DrawContext_GetSize(drawContext); // Draw custom content. auto path = OH_Drawing_PathCreate(); OH_Drawing_PathMoveTo(path, size.width / NUM_4, size.height / NUM_4); OH_Drawing_PathLineTo(path, size.width * NUM_3 / NUM_4, size.height / NUM_4); OH_Drawing_PathLineTo(path, size.width * NUM_3 / NUM_4, size.height * NUM_3 / NUM_4); OH_Drawing_PathLineTo(path, size.width / NUM_4, size.height * NUM_3 / NUM_4); OH_Drawing_PathLineTo(path, size.width / NUM_4, size.height / NUM_4); OH_Drawing_PathClose(path); auto brush = OH_Drawing_BrushCreate(); OH_Drawing_BrushSetColor(brush, 0xff2787D9); // Light blue OH_Drawing_CanvasAttachBrush(drawCanvas, brush); OH_Drawing_CanvasDrawPath(drawCanvas, path); // Release resources. OH_Drawing_BrushDestroy(brush); OH_Drawing_PathDestroy(path); } void OnDrawFront(ArkUI_NodeCustomEvent *event) { auto drawContext = OH_ArkUI_NodeCustomEvent_GetDrawContextInDraw(event); // Obtain the graphics drawing object. auto drawCanvas = reinterpret_cast<OH_Drawing_Canvas *>(OH_ArkUI_DrawContext_GetCanvas(drawContext)); // Obtain the component size. auto size = OH_ArkUI_DrawContext_GetSize(drawContext); // Draw custom content. auto path = OH_Drawing_PathCreate(); OH_Drawing_PathMoveTo(path, size.width / NUM_3, size.height / NUM_3); OH_Drawing_PathLineTo(path, size.width * NUM_2 / NUM_3, size.height / NUM_3); OH_Drawing_PathLineTo(path, size.width * NUM_2 / NUM_3, size.height * NUM_2 / NUM_3); OH_Drawing_PathLineTo(path, size.width / NUM_3, size.height * NUM_2 / NUM_3); OH_Drawing_PathLineTo(path, size.width / NUM_3, size.height / NUM_3); OH_Drawing_PathClose(path); auto brush = OH_Drawing_BrushCreate(); OH_Drawing_BrushSetColor(brush, 0xFF004AAF); // Dark blue OH_Drawing_CanvasAttachBrush(drawCanvas, brush); OH_Drawing_CanvasDrawPath(drawCanvas, path); // Release resources. OH_Drawing_BrushDestroy(brush); OH_Drawing_PathDestroy(path); } // ... }; } // namespace NativeModule #endif // MYAPPLICATION_ARKUICUSTOMNODE_H -
Use the custom drawing component and custom container to create a sample UI.
#include <arkui/native_node_napi.h> #include <arkui/native_type.h> #include <js_native_api.h> #include "NativeEntry.h" #include "ArkUICustomContainerNode.h" #include "ArkUICustomNode.h" // Declare global environment variables. static napi_env g_env = nullptr; // ... namespace NativeModule { // ... #define SIZE_150 150 // ... napi_value CreateNativeRoot(napi_env env, napi_callback_info info) { size_t argc = 1; napi_value args[1] = {nullptr}; napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); // Obtain the NodeContent object. ArkUI_NodeContentHandle contentHandle; OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle); NativeEntry::GetInstance()->SetContentHandle(contentHandle); // Create a custom container and a custom drawing component. auto node = std::make_shared<ArkUICustomContainerNode>(); node->SetBackgroundColor(0xFFD5D5D5); // Light gray. auto customNode = std::make_shared<ArkUICustomNode>(); customNode->SetBackgroundColor(0xFF707070); // Dark gray. customNode->SetWidth(SIZE_150); customNode->SetHeight(SIZE_150); node->AddChild(customNode); // Keep the native-side object in the management class to maintain its lifecycle. NativeEntry::GetInstance()->SetRootNode(node); g_env = env; return nullptr; } napi_value DestroyNativeRoot(napi_env env, napi_callback_info info) { // Release the native-side object from the management class. NativeEntry::GetInstance()->DisposeRootNode(); return nullptr; } } // namespace NativeModule
