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

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.

  1. Prepare a project as instructed in Custom Layout Container.

  2. 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
    
  3. 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
    

customDrawLayer