构建自定义组件
=======

ArkUI开发框架在NDK接口提供了自定义UI组件的能力，这些能力包括自定义测算，自定义布局和自定义绘制。开发者通过注册相关自定义回调事件接入ArkUI开发框架的布局渲染流程，这些事件需要使用[registerNodeCustomEvent](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-arkui-nativemodule-arkui-nativenodeapi-1#registernodecustomevent)来进行声明，并通过[addNodeCustomEventReceiver](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-arkui-nativemodule-arkui-nativenodeapi-1#addnodecustomeventreceiver)函数添加组件自定义事件的监听器，在该监听器的回调函数中处理相关自定义测算，自定义布局和自定义绘制逻辑。

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/10/v3/gSzY3nQTR5Wcpxnpin9l1g/note_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260612T073317Z&HW-CC-Expire=86400&HW-CC-Sign=3BBA1312AB74AED09E588A60126ECE8D35774DE3130D209E51DC6052FCA4B4F3)

*   自定义组件事件注册需要[addNodeCustomEventReceiver](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-arkui-nativemodule-arkui-nativenodeapi-1#addnodecustomeventreceiver)声明监听器注册和[registerNodeCustomEvent](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-arkui-nativemodule-arkui-nativenodeapi-1#registernodecustomevent)声明需要的自定义事件类型，监听器只能监听已声明的事件。
    
*   需要关注事件的反注册逻辑，如在组件销毁前调用[removeNodeCustomEventReceiver](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-arkui-nativemodule-arkui-nativenodeapi-1#removenodecustomeventreceiver)移除事件监听器，[unregisterNodeCustomEvent](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-arkui-nativemodule-arkui-nativenodeapi-1#unregisternodecustomevent)通知ArkUI框架已监听的自定义组件事件不再需要监听。
    
*   [addNodeCustomEventReceiver](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-arkui-nativemodule-arkui-nativenodeapi-1#addnodecustomeventreceiver)可以添加多个函数指针，每个函数指针都会在对应事件触发时触发，对应的[removeNodeCustomEventReceiver](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-arkui-nativemodule-arkui-nativenodeapi-1#removenodecustomeventreceiver)需要传递对应的函数指针用于移除监听。
    
*   [registerNodeCustomEventReceiver](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-arkui-nativemodule-arkui-nativenodeapi-1#registernodecustomeventreceiver)是全局监听函数，不同于[addNodeCustomEventReceiver](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-arkui-nativemodule-arkui-nativenodeapi-1#addnodecustomeventreceiver)，[registerNodeCustomEventReceiver](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-arkui-nativemodule-arkui-nativenodeapi-1#registernodecustomeventreceiver)能够监听所有Native组件的自定义事件触发，但只能传递一个函数指针，多次调用使用最后一次的函数指针进行回调，释放时使用unregisterNodeCustomEventReceiver进行反注册。
    
*   自定义组件相关接口（[measureNode](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-arkui-nativemodule-arkui-nativenodeapi-1#measurenode)、[layoutNode](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-arkui-nativemodule-arkui-nativenodeapi-1#layoutnode)、[setMeasuredSize](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-arkui-nativemodule-arkui-nativenodeapi-1#setmeasuredsize)、[setLayoutPosition](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-arkui-nativemodule-arkui-nativenodeapi-1#setlayoutposition)）仅允许在对应的自定义事件（[ARKUI\_NODE\_CUSTOM\_EVENT\_ON\_MEASURE、ARKUI\_NODE\_CUSTOM\_EVENT\_ON\_LAYOUT](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-native-node-h#arkui_nodecustomeventtype)）回调中使用。
    

#### 自定义布局容器

以下示例创建了一个自定义容器，该容器将子组件最大值加上额外边距作为自身大小，同时对子组件进行居中排布。

**图1** 自定义容器组件

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/aa/v3/BXF8oX4dRqaX3C7rWHsy6g/zh-cn_image_0000002625187643.png?HW-CC-KV=V1&HW-CC-Date=20260612T073317Z&HW-CC-Expire=86400&HW-CC-Sign=AAC4916B9ED24064F669F07EEDA99AC73CCD435561B3BC07D2ABF1BE2A062B73)

1.  按照[接入ArkTS页面](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ndk-access-the-arkts-page)创建前置工程。
    
2.  创建自定义容器组件封装对象。
    
    // ArkUICustomContainerNode.h
    // 自定义容器组件示例
    
    #ifndef MYAPPLICATION\_ARKUICUSTOMCONTAINERNODE\_H
    #define MYAPPLICATION\_ARKUICUSTOMCONTAINERNODE\_H
    
    #include "ArkUINode.h"
    
    namespace NativeModule {
    
        class ArkUICustomContainerNode : public ArkUINode {
        public:
            // 使用自定义组件类型ARKUI\_NODE\_CUSTOM创建组件。
            ArkUICustomContainerNode()
                : ArkUINode((NativeModuleInstance::GetInstance()->GetNativeNodeAPI())->createNode(ARKUI\_NODE\_CUSTOM))
            {
                // 注册自定义事件监听器。
                nativeModule\_->addNodeCustomEventReceiver(handle\_, OnStaticCustomEvent);
                // 声明自定义事件并传递自身作为自定义数据。
                nativeModule\_->registerNodeCustomEvent(handle\_, ARKUI\_NODE\_CUSTOM\_EVENT\_ON\_MEASURE, 0, this);
                nativeModule\_->registerNodeCustomEvent(handle\_, ARKUI\_NODE\_CUSTOM\_EVENT\_ON\_LAYOUT, 0, this);
            }
    
            ~ArkUICustomContainerNode() override
            {
                // 反注册自定义事件监听器。
                nativeModule\_->removeNodeCustomEventReceiver(handle\_, OnStaticCustomEvent);
                // 取消声明自定义事件。
                nativeModule\_->unregisterNodeCustomEvent(handle\_, ARKUI\_NODE\_CUSTOM\_EVENT\_ON\_MEASURE);
                nativeModule\_->unregisterNodeCustomEvent(handle\_, ARKUI\_NODE\_CUSTOM\_EVENT\_ON\_LAYOUT);
            }
    
            void SetPadding(int32\_t padding)
            {
                padding\_ = padding;
                // 自定义属性事件更新需要主动调用标记脏区接口。
                nativeModule\_->markDirty(handle\_, NODE\_NEED\_MEASURE);
            }
    
        private:
            static void OnStaticCustomEvent(ArkUI\_NodeCustomEvent \*event)
            {
                // 获取组件实例对象，调用相关实例方法。
                auto customNode = reinterpret\_cast<ArkUICustomContainerNode \*>(OH\_ArkUI\_NodeCustomEvent\_GetUserData(event));
                auto type = OH\_ArkUI\_NodeCustomEvent\_GetEventType(event);
                switch (type) {
                    case ARKUI\_NODE\_CUSTOM\_EVENT\_ON\_MEASURE:
                        customNode->OnMeasure(event);
                        break;
                    case ARKUI\_NODE\_CUSTOM\_EVENT\_ON\_LAYOUT:
                        customNode->OnLayout(event);
                        break;
                    default:
                        break;
                }
            }
    
            // 自定义测算逻辑。
            void OnMeasure(ArkUI\_NodeCustomEvent \*event)
            {
                auto layoutConstrain = OH\_ArkUI\_NodeCustomEvent\_GetLayoutConstraintInMeasure(event);
                // 创建子节点布局限制，复用父组件布局中的百分比参考值。
                auto childLayoutConstrain = OH\_ArkUI\_LayoutConstraint\_Copy(layoutConstrain);
                int32\_t maxConstrain = 1000;
                OH\_ArkUI\_LayoutConstraint\_SetMaxHeight(childLayoutConstrain, maxConstrain);
                OH\_ArkUI\_LayoutConstraint\_SetMaxWidth(childLayoutConstrain, maxConstrain);
                OH\_ArkUI\_LayoutConstraint\_SetMinHeight(childLayoutConstrain, 0);
                OH\_ArkUI\_LayoutConstraint\_SetMinWidth(childLayoutConstrain, 0);
    
                // 测算子节点获取子节点最大值。
                auto totalSize = nativeModule\_->getTotalChildCount(handle\_);
                int32\_t maxWidth = 0;
                int32\_t maxHeight = 0;
                for (uint32\_t i = 0; i < totalSize; i++) {
                    auto child = nativeModule\_->getChildAt(handle\_, i);
                    // 调用测算接口测算Native组件。
                    nativeModule\_->measureNode(child, childLayoutConstrain);
                    auto size = nativeModule\_->getMeasuredSize(child);
                    if (size.width > maxWidth) {
                        maxWidth = size.width;
                    }
                    if (size.height > maxHeight) {
                        maxHeight = size.height;
                    }
                }
                // 自定义测算为所有子节点大小加固定边距。该自定义节点最终的尺寸以此处设置的值为准。
                const int paddingMultiplier = 2;
                nativeModule\_->setMeasuredSize(handle\_, maxWidth + paddingMultiplier \* padding\_,
                                               maxHeight + paddingMultiplier \* padding\_);
            }
    
            void OnLayout(ArkUI\_NodeCustomEvent \*event)
            {
                // 获取父组件期望位置并设置。
                auto position = OH\_ArkUI\_NodeCustomEvent\_GetPositionInLayout(event);
                nativeModule\_->setLayoutPosition(handle\_, position.x, position.y);
    
                // 设置子组件居中对齐。
                auto totalSize = nativeModule\_->getTotalChildCount(handle\_);
                auto selfSize = nativeModule\_->getMeasuredSize(handle\_);
                for (uint32\_t i = 0; i < totalSize; i++) {
                    auto child = nativeModule\_->getChildAt(handle\_, i);
                    // 获取子组件大小。
                    auto childSize = nativeModule\_->getMeasuredSize(child);
                    // 布局子组件位置。
                    int32\_t horizontalMargin = (selfSize.width - childSize.width) / 2;
                    int32\_t verticalMargin = (selfSize.height - childSize.height) / 2;
                    nativeModule\_->layoutNode(child, horizontalMargin, verticalMargin);
                }
            }
    
            int32\_t padding\_ = 100;
        };
    
    } // namespace NativeModule
    
    #endif // MYAPPLICATION\_ARKUICUSTOMCONTAINERNODE\_H
    
3.  使用自定义容器创建带文本的示例界面。
    
    #include "NativeEntry.h"
    
    #include "ArkUICustomContainerNode.h"
    #include "ArkUITextNode.h"
    #include "UITimer.h"
    
    #include <arkui/native\_node\_napi.h>
    #include <arkui/native\_type.h>
    #include <js\_native\_api.h>
    
    namespace NativeModule {
        namespace {
            napi\_env g\_env;
        } // namespace
    
        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);
    
            ArkUI\_NodeContentHandle contentHandle;
            OH\_ArkUI\_GetNodeContentFromNapiValue(env, args\[0\], &contentHandle);
            NativeEntry::GetInstance()->SetContentHandle(contentHandle);
    
            // 创建自定义容器和文本组件。
            auto node = std::make\_shared<ArkUICustomContainerNode>();
            node->SetBackgroundColor(0xFFE0FFFF);
            auto textNode = std::make\_shared<ArkUITextNode>();
            textNode->SetTextContent("CustomContainer Example");
            const int32\_t fontSize = 16;
            textNode->SetFontSize(fontSize);
            textNode->SetBackgroundColor(0xFFfffacd);
            textNode->SetTextAlign(ARKUI\_TEXT\_ALIGNMENT\_CENTER);
            node->AddChild(textNode);
            auto onClick = \[\](ArkUI\_NodeEvent \*event) {
                auto textNode = (ArkUITextNode \*)OH\_ArkUI\_NodeEvent\_GetUserData(event);
                textNode->SetFontColor(0xFF00FF7F);
            };
            textNode->RegisterOnClick(onClick, textNode.get());
    
            // 保持Native侧对象到管理类中，维护生命周期。
            NativeEntry::GetInstance()->SetRootNode(node);
            g\_env = env;
            return nullptr;
        }
    
        napi\_value DestroyNativeRoot(napi\_env env, napi\_callback\_info info)
        {
            // 从管理类中释放Native侧对象。
            NativeEntry::GetInstance()->DisposeRootNode();
            return nullptr;
        }
    
    } // namespace NativeModule
    
4.  修改CMakeLists.txt，添加链接库。
    
      # CMakeLists.txt
    
      # the minimum version of CMake.
      cmake\_minimum\_required(VERSION 3.4.1)
      project(testndk)
    
      set(NATIVERENDER\_ROOT\_PATH ${CMAKE\_CURRENT\_SOURCE\_DIR})
    
      include\_directories(${NATIVERENDER\_ROOT\_PATH}
                           ${NATIVERENDER\_ROOT\_PATH}/include)
    
      add\_library(entry SHARED NativeEntry.cpp napi\_init.cpp)
      # target\_link\_libraries(entry PUBLIC libace\_napi.z.so, libace\_ndk.z.so, libhilog\_ndk.z.so)
    
      find\_library(
           # Sets the name of the path variable.
           hilog-lib
           # Specifies the name of the NDK library that
           # you want CMake to locate.
           hilog\_ndk.z
       )
    
      find\_library(
           # Sets the name of the path variable.
           libace-lib
           # Specifies the name of the NDK library that
           # you want CMake to locate.
           ace\_ndk.z
       )
    
      find\_library(
           # Sets the name of the path variable.
           libnapi-lib
           # Specifies the name of the NDK library that
           # you want CMake to locate.
           ace\_napi.z
       )
    
       find\_library(
            # Sets the name of the path variable.
            libuv-lib
            uv
        )
    
      target\_link\_libraries(entry PUBLIC
           ${hilog-lib} ${libace-lib} ${libnapi-lib} ${libuv-lib} )
    

#### 自定义绘制组件

以下示例创建了一个自定义绘制组件，该绘制组件能够绘制自定义矩形，并使用上述自定义容器进行布局排布。

**图2** 自定义绘制组件

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/c5/v3/IWKIBJxhQmyDRSZN3pVWzA/zh-cn_image_0000002625107787.png?HW-CC-KV=V1&HW-CC-Date=20260612T073317Z&HW-CC-Expire=86400&HW-CC-Sign=369835D5B90DB7336C4C692C299D26B103CAEAA57ED1ACB3D714A0DB1E245827)

1.  按照[自定义布局容器](#自定义布局容器)章节准备前置工程。
    
2.  创建自定义绘制组件封装对象。
    
    // ArkUICustomNode.h
    // 自定义绘制组件示例
    
    #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:
            // 使用自定义组件类型ARKUI\_NODE\_CUSTOM创建组件。
            ArkUICustomNode()
                : ArkUINode((NativeModuleInstance::GetInstance()->GetNativeNodeAPI())->createNode(ARKUI\_NODE\_CUSTOM))
            {
                // 注册自定义事件监听器。
                nativeModule\_->addNodeCustomEventReceiver(handle\_, OnStaticCustomEvent);
                // 声明自定义事件并传递自身作为自定义数据。
                nativeModule\_->registerNodeCustomEvent(handle\_, ARKUI\_NODE\_CUSTOM\_EVENT\_ON\_DRAW, 0, this);
            }
    
            ~ArkUICustomNode() override
            {
                // 反注册自定义事件监听器。
                nativeModule\_->removeNodeCustomEventReceiver(handle\_, OnStaticCustomEvent);
                // 取消声明自定义事件。
                nativeModule\_->unregisterNodeCustomEvent(handle\_, ARKUI\_NODE\_CUSTOM\_EVENT\_ON\_DRAW);
            }
    
            void SetRectColor(uint32\_t color)
            {
                color\_ = color;
                // 自定义绘制属性变更需要主动通知框架。
                nativeModule\_->markDirty(handle\_, NODE\_NEED\_RENDER);
            }
    
        private:
            static void OnStaticCustomEvent(ArkUI\_NodeCustomEvent \*event)
            {
                // 获取组件实例对象，调用相关实例方法。
                auto customNode = reinterpret\_cast<ArkUICustomNode \*>(OH\_ArkUI\_NodeCustomEvent\_GetUserData(event));
                auto type = OH\_ArkUI\_NodeCustomEvent\_GetEventType(event);
                switch (type) {
                    case ARKUI\_NODE\_CUSTOM\_EVENT\_ON\_DRAW:
                        customNode->OnDraw(event);
                        break;
                    default:
                        break;
                }
            }
    
            // 自定义绘制逻辑。
            void OnDraw(ArkUI\_NodeCustomEvent \*event)
            {
                auto drawContext = OH\_ArkUI\_NodeCustomEvent\_GetDrawContextInDraw(event);
                // 获取图形绘制对象。
                auto drawCanvas = reinterpret\_cast<OH\_Drawing\_Canvas \*>(OH\_ArkUI\_DrawContext\_GetCanvas(drawContext));
                // 获取组件大小。
                auto size = OH\_ArkUI\_DrawContext\_GetSize(drawContext);
                // 绘制自定义内容。
                auto path = OH\_Drawing\_PathCreate();
                const float kQuarter = 0.25f;
                const float kThreeQuarters = 0.75f;
                OH\_Drawing\_PathMoveTo(path, size.width \* kQuarter, size.height \* kQuarter);
                OH\_Drawing\_PathLineTo(path, size.width \* kThreeQuarters, size.height \* kQuarter);
                OH\_Drawing\_PathLineTo(path, size.width \* kThreeQuarters, size.height \* kThreeQuarters);
                OH\_Drawing\_PathLineTo(path, size.width \* kQuarter, size.height \* kThreeQuarters);
                OH\_Drawing\_PathLineTo(path, size.width \* kQuarter, size.height \* kQuarter);
                OH\_Drawing\_PathClose(path);
                auto brush = OH\_Drawing\_BrushCreate();
                OH\_Drawing\_BrushSetColor(brush, color\_);
                OH\_Drawing\_CanvasAttachBrush(drawCanvas, brush);
                OH\_Drawing\_CanvasDrawPath(drawCanvas, path);
                // 释放资源
                OH\_Drawing\_BrushDestroy(brush);
                OH\_Drawing\_PathDestroy(path);
            }
    
            uint32\_t color\_ = 0xFFFFE4B5;
        };
    
    } // namespace NativeModule
    
    #endif // MYAPPLICATION\_ARKUICUSTOMNODE\_H
    
3.  使用自定义绘制组件和自定义容器创建示例界面。
    
    #include "NativeEntry.h"
    
    #include "ArkUICustomContainerNode.h"
    #include "ArkUICustomNode.h"
    
    #include <arkui/native\_node\_napi.h>
    #include <arkui/native\_type.h>
    #include <js\_native\_api.h>
    #include "UITimer.h"
    
    namespace NativeModule {
        namespace {
            napi\_env g\_env;
        } // namespace
    
        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);
    
            // 获取NodeContent
            ArkUI\_NodeContentHandle contentHandle;
            OH\_ArkUI\_GetNodeContentFromNapiValue(env, args\[0\], &contentHandle);
            NativeEntry::GetInstance()->SetContentHandle(contentHandle);
    
            // 创建自定义容器和自定义绘制组件。
            auto node = std::make\_shared<ArkUICustomContainerNode>();
            node->SetBackgroundColor(0xFFE0FFFF);
            auto customNode = std::make\_shared<ArkUICustomNode>();
            customNode->SetBackgroundColor(0xFFD3D3D3);
            const int width = 150;
            const int height = 150;
            customNode->SetWidth(width);
            customNode->SetHeight(height);
            node->AddChild(customNode);
            auto onClick = \[\](ArkUI\_NodeEvent \*event) {
                auto customNode = (ArkUICustomNode \*)OH\_ArkUI\_NodeEvent\_GetUserData(event);
                customNode->SetRectColor(0xFF00FF7F);
            };
            customNode->RegisterOnClick(onClick, customNode.get());
    
            // 保持Native侧对象到管理类中，维护生命周期。
            NativeEntry::GetInstance()->SetRootNode(node);
            g\_env = env;
            return nullptr;
        }
    
        napi\_value DestroyNativeRoot(napi\_env env, napi\_callback\_info info)
        {
            // 从管理类中释放Native侧对象。
            NativeEntry::GetInstance()->DisposeRootNode();
            return nullptr;
        }
    
    } // namespace NativeModule
    
4.  修改CMakeLists.txt，添加链接库。
    
      # CMakeLists.txt
    
      # the minimum version of CMake.
      cmake\_minimum\_required(VERSION 3.4.1)
      project(testndk)
    
      set(NATIVERENDER\_ROOT\_PATH ${CMAKE\_CURRENT\_SOURCE\_DIR})
    
      include\_directories(${NATIVERENDER\_ROOT\_PATH}
                           ${NATIVERENDER\_ROOT\_PATH}/include)
    
      add\_library(entry SHARED NativeEntry.cpp napi\_init.cpp)
      # target\_link\_libraries(entry PUBLIC libace\_napi.z.so, libace\_ndk.z.so, libhilog\_ndk.z.so)
    
      find\_library(
           # Sets the name of the path variable.
           hilog-lib
           # Specifies the name of the NDK library that
           # you want CMake to locate.
           hilog\_ndk.z
       )
    
      find\_library(
           # Sets the name of the path variable.
           libace-lib
           # Specifies the name of the NDK library that
           # you want CMake to locate.
           ace\_ndk.z
       )
    
      find\_library(
           # Sets the name of the path variable.
           libnapi-lib
           # Specifies the name of the NDK library that
           # you want CMake to locate.
           ace\_napi.z
       )
    
       find\_library(
            # Sets the name of the path variable.
            libuv-lib
            uv
        )
    
      target\_link\_libraries(entry PUBLIC
           ${hilog-lib} ${libace-lib} ${libnapi-lib} ${libuv-lib} libnative\_drawing.so)
    

#### 不规则网格布局示例

以下示例创建了一个不规则网格布局容器，支持不同大小的网格单元，实现类似瀑布流的布局效果。完整示例请参考[CustomDrawIrregularSample](https://gitcode.com/HarmonyOS_Samples/guide-snippets/tree/master/ArkUISample/NativeType/CustomDrawIrregularSample)。

**图3** 不规则网格布局效果

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/6a/v3/pUjTFd9xSs6QVqaza2BxOw/zh-cn_image_0000002594668270.jpg?HW-CC-KV=V1&HW-CC-Date=20260612T073317Z&HW-CC-Expire=86400&HW-CC-Sign=5209EEBF6EE57A80FA107367EE23F6141DA4B5925B674EC2AC3643878AF0F37B)

1.  按照[自定义布局容器](#自定义布局容器)章节准备前置工程。
    
2.  创建不规则网格布局容器组件封装对象。
    
    // ArkUIIrregularGridNode.h
    // 不规则网格布局容器示例
    
    #ifndef MYAPPLICATION\_ARKUIIRREGULARGRIDNODE\_H
    #define MYAPPLICATION\_ARKUIIRREGULARGRIDNODE\_H
    
    #include "ArkUINode.h"
    #include <vector>
    #include <map>
    
    namespace NativeModule {
    
    // 网格单元配置
    struct GridItemConfig {
        int32\_t rowSpan = 1;    // 占据的行数
        int32\_t columnSpan = 1; // 占据的列数
    };
    
    class ArkUIIrregularGridNode : public ArkUINode {
    public:
        // 使用自定义组件类型ARKUI\_NODE\_CUSTOM创建组件
        ArkUIIrregularGridNode()
            : ArkUINode((NativeModuleInstance::GetInstance()->GetNativeNodeAPI())->createNode(ARKUI\_NODE\_CUSTOM))
        {
            // 注册自定义事件监听器
            nativeModule\_->addNodeCustomEventReceiver(handle\_, OnStaticCustomEvent);
            // 声明自定义事件并传递自身作为自定义数据
            nativeModule\_->registerNodeCustomEvent(handle\_, ARKUI\_NODE\_CUSTOM\_EVENT\_ON\_MEASURE, 0, this);
            nativeModule\_->registerNodeCustomEvent(handle\_, ARKUI\_NODE\_CUSTOM\_EVENT\_ON\_LAYOUT, 0, this);
        }
    
        ~ArkUIIrregularGridNode() override
        {
            // 反注册自定义事件监听器
            nativeModule\_->removeNodeCustomEventReceiver(handle\_, OnStaticCustomEvent);
            // 取消声明自定义事件
            nativeModule\_->unregisterNodeCustomEvent(handle\_, ARKUI\_NODE\_CUSTOM\_EVENT\_ON\_MEASURE);
            nativeModule\_->unregisterNodeCustomEvent(handle\_, ARKUI\_NODE\_CUSTOM\_EVENT\_ON\_LAYOUT);
        }
    
        // 设置列数
        void SetColumnCount(int32\_t count)
        {
            columnCount\_ = count;
            nativeModule\_->markDirty(handle\_, NODE\_NEED\_MEASURE);
        }
    
        // 设置网格间距
        void SetGap(int32\_t gap)
        {
            gap\_ = gap;
            nativeModule\_->markDirty(handle\_, NODE\_NEED\_MEASURE);
        }
    
        // 设置子组件的网格配置
        void SetItemConfig(ArkUI\_NodeHandle child, int32\_t rowSpan, int32\_t columnSpan)
        {
            GridItemConfig config;
            config.rowSpan = rowSpan;
            config.columnSpan = columnSpan;
            itemConfigs\_\[child\] = config;
            nativeModule\_->markDirty(handle\_, NODE\_NEED\_MEASURE);
        }
    
    private:
        static void OnStaticCustomEvent(ArkUI\_NodeCustomEvent \*event)
        {
            // 获取组件实例对象，调用相关实例方法
            auto customNode = reinterpret\_cast<ArkUIIrregularGridNode \*>(OH\_ArkUI\_NodeCustomEvent\_GetUserData(event));
            auto type = OH\_ArkUI\_NodeCustomEvent\_GetEventType(event);
            switch (type) {
                case ARKUI\_NODE\_CUSTOM\_EVENT\_ON\_MEASURE:
                    customNode->OnMeasure(event);
                    break;
                case ARKUI\_NODE\_CUSTOM\_EVENT\_ON\_LAYOUT:
                    customNode->OnLayout(event);
                    break;
                default:
                    break;
            }
        }
    
        // 测算单个子组件并更新列高信息
        void MeasureChild(ArkUI\_NodeHandle child, int32\_t cellWidth,
            ArkUI\_LayoutConstraint \*childConstraint, std::vector<int32\_t> &columnHeights)
        {
            GridItemConfig config = {1, 1};
            auto it = itemConfigs\_.find(child);
            if (it != itemConfigs\_.end()) {
                config = it->second;
            }
            if (config.columnSpan > columnCount\_) {
                config.columnSpan = columnCount\_;
            }
    
            int32\_t startColumn = FindLowestColumn(columnHeights, config.columnSpan);
            int32\_t startY = 0;
            for (int32\_t col = startColumn; col < startColumn + config.columnSpan && col < columnCount\_; col++) {
                if (columnHeights\[col\] > startY) {
                    startY = columnHeights\[col\];
                }
            }
    
            int32\_t itemWidth = cellWidth \* config.columnSpan + gap\_ \* (config.columnSpan - 1);
            OH\_ArkUI\_LayoutConstraint\_SetMaxWidth(childConstraint, itemWidth);
            OH\_ArkUI\_LayoutConstraint\_SetMinWidth(childConstraint, itemWidth);
            nativeModule\_->measureNode(child, childConstraint);
            auto size = nativeModule\_->getMeasuredSize(child);
    
            LayoutItemInfo info;
            info.x = startColumn \* (cellWidth + gap\_);
            info.y = startY;
            info.width = size.width;
            info.height = size.height;
            layoutInfo\_.push\_back(info);
    
            int32\_t newHeight = startY + size.height + gap\_;
            for (int32\_t col = startColumn; col < startColumn + config.columnSpan && col < columnCount\_; col++) {
                columnHeights\[col\] = newHeight;
            }
        }
    
        // 自定义测算逻辑：不规则网格布局
        void OnMeasure(ArkUI\_NodeCustomEvent \*event)
        {
            auto layoutConstrain = OH\_ArkUI\_NodeCustomEvent\_GetLayoutConstraintInMeasure(event);
            int32\_t maxWidth = OH\_ArkUI\_LayoutConstraint\_GetMaxWidth(layoutConstrain);
    
            int32\_t totalGap = gap\_ \* (columnCount\_ - 1);
            int32\_t cellWidth = (maxWidth - totalGap) / columnCount\_;
    
            auto childConstraint = OH\_ArkUI\_LayoutConstraint\_Copy(layoutConstrain);
            std::vector<int32\_t> columnHeights(columnCount\_, 0);
            layoutInfo\_.clear();
    
            auto totalSize = nativeModule\_->getTotalChildCount(handle\_);
            for (uint32\_t i = 0; i < totalSize; i++) {
                auto child = nativeModule\_->getChildAt(handle\_, i);
                MeasureChild(child, cellWidth, childConstraint, columnHeights);
            }
    
            int32\_t maxHeight = 0;
            for (int32\_t height : columnHeights) {
                if (height > maxHeight) {
                    maxHeight = height;
                }
            }
            if (maxHeight > gap\_) {
                maxHeight -= gap\_;
            }
    
            nativeModule\_->setMeasuredSize(handle\_, maxWidth, maxHeight);
            OH\_ArkUI\_LayoutConstraint\_Dispose(childConstraint);
        }
    
        void OnLayout(ArkUI\_NodeCustomEvent \*event)
        {
            // 获取父组件期望位置并设置
            auto position = OH\_ArkUI\_NodeCustomEvent\_GetPositionInLayout(event);
            nativeModule\_->setLayoutPosition(handle\_, position.x, position.y);
    
            // 布局子组件
            auto totalSize = nativeModule\_->getTotalChildCount(handle\_);
            for (uint32\_t i = 0; i < totalSize && i < layoutInfo\_.size(); i++) {
                auto child = nativeModule\_->getChildAt(handle\_, i);
                nativeModule\_->layoutNode(child, layoutInfo\_\[i\].x, layoutInfo\_\[i\].y);
            }
        }
    
        // 找到最矮的列，确保可以放下指定列跨度的项
        int32\_t FindLowestColumn(const std::vector<int32\_t>& columnHeights, int32\_t columnSpan)
        {
            int32\_t lowestColumn = 0;
            int32\_t lowestHeight = INT32\_MAX;
    
            // 遍历所有可能的起始列
            for (int32\_t col = 0; col <= columnCount\_ - columnSpan; col++) {
                // 找到这个范围内最高的列
                int32\_t maxHeightInRange = 0;
                for (int32\_t i = col; i < col + columnSpan; i++) {
                    if (columnHeights\[i\] > maxHeightInRange) {
                        maxHeightInRange = columnHeights\[i\];
                    }
                }
    
                // 如果这个范围的最高点比当前最低点还低，更新最低列
                if (maxHeightInRange < lowestHeight) {
                    lowestHeight = maxHeightInRange;
                    lowestColumn = col;
                }
            }
    
            return lowestColumn;
        }
    
        struct LayoutItemInfo {
            int32\_t x;
            int32\_t y;
            int32\_t width;
            int32\_t height;
        };
    
        int32\_t columnCount\_ = 3;
        int32\_t gap\_ = 10;
        std::map<ArkUI\_NodeHandle, GridItemConfig> itemConfigs\_;
        std::vector<LayoutItemInfo> layoutInfo\_;
    };
    
    } // namespace NativeModule
    
    #endif // MYAPPLICATION\_ARKUIIRREGULARGRIDNODE\_H
    
3.  使用不规则网格布局容器创建示例界面。
    
    #include "NativeEntry.h"
    
    #include "ArkUIIrregularGridNode.h"
    #include "ArkUINode.h"
    
    #include <arkui/native\_node\_napi.h>
    #include <arkui/native\_type.h>
    #include <js\_native\_api.h>
    #include <utility>
    #include <vector>
    
    namespace NativeModule {
    namespace {
    napi\_env g\_env = nullptr;
    
    constexpr uint32\_t GRID\_BACKGROUND\_COLOR = 0xFFF5F5F5;
    constexpr int32\_t GRID\_COLUMN\_COUNT = 4;
    constexpr int32\_t GRID\_GAP = 8;
    constexpr float GRID\_ITEM\_RADIUS = 8.0f;
    constexpr float GRID\_ITEM\_BORDER\_WIDTH = 1.0f;
    constexpr uint32\_t GRID\_ITEM\_BORDER\_COLOR = 0xFFCCCCCC;
    constexpr float GRID\_ITEM\_BASE\_HEIGHT = 60.0f;
    constexpr float GRID\_ITEM\_HEIGHT\_STEP = 40.0f;
    
    using GridItemSize = std::pair<int32\_t, int32\_t>;
    
    const std::vector<GridItemSize>& GetGridItemSizes()
    {
        static const std::vector<GridItemSize> itemSizes = {
            {1, 1}, // 小方块
            {2, 1}, // 竖长条
            {1, 3}, // 横长条
            {2, 2}, // 大方块
            {1, 1}, // 小方块
            {1, 2}, // 横条
            {3, 1}, // 很长的竖条
        };
        return itemSizes;
    }
    
    const std::vector<uint32\_t>& GetGridItemColors()
    {
        static const std::vector<uint32\_t> colors = {
            0xFF64B5F6, // 蓝色
            0xFFE57373, // 红色
            0xFF81C784, // 绿色
            0xFFFFB74D, // 橙色
            0xFF9575CD, // 紫色
            0xFF4DB6AC, // 青色
            0xFFFFD54F, // 黄色
            0xFFF06292, // 粉色
            0xFF7986CB, // 靛蓝
            0xFFA1887F, // 棕色
        };
        return colors;
    }
    
    void SetNodeColorAttribute(ArkUI\_NativeNodeAPI\_1\* nodeAPI, ArkUI\_NodeHandle node, uint32\_t color)
    {
        ArkUI\_NumberValue bgColor\[\] = {{.u32 = color}};
        ArkUI\_AttributeItem bgColorItem = {bgColor, 1};
        nodeAPI->setAttribute(node, NODE\_BACKGROUND\_COLOR, &bgColorItem);
    }
    
    void SetNodeBorderRadiusAttribute(ArkUI\_NativeNodeAPI\_1\* nodeAPI, ArkUI\_NodeHandle node, float radius)
    {
        ArkUI\_NumberValue radiusValue\[\] = {{.f32 = radius}};
        ArkUI\_AttributeItem radiusItem = {radiusValue, 1};
        nodeAPI->setAttribute(node, NODE\_BORDER\_RADIUS, &radiusItem);
    }
    
    void SetNodeBorderStyle(ArkUI\_NativeNodeAPI\_1\* nodeAPI, ArkUI\_NodeHandle node)
    {
        ArkUI\_NumberValue borderWidth\[\] = {{.f32 = GRID\_ITEM\_BORDER\_WIDTH}};
        ArkUI\_AttributeItem borderWidthItem = {borderWidth, 1};
        nodeAPI->setAttribute(node, NODE\_BORDER\_WIDTH, &borderWidthItem);
    
        ArkUI\_NumberValue borderColor\[\] = {{.u32 = GRID\_ITEM\_BORDER\_COLOR}};
        ArkUI\_AttributeItem borderColorItem = {borderColor, 1};
        nodeAPI->setAttribute(node, NODE\_BORDER\_COLOR, &borderColorItem);
    }
    
    void SetNodeHeightByRowSpan(ArkUI\_NativeNodeAPI\_1\* nodeAPI, ArkUI\_NodeHandle node, int32\_t rowSpan)
    {
        float minHeight = GRID\_ITEM\_BASE\_HEIGHT + (rowSpan - 1) \* GRID\_ITEM\_HEIGHT\_STEP;
        ArkUI\_NumberValue minHeightValue\[\] = {{.f32 = minHeight}};
        ArkUI\_AttributeItem minHeightItem = {minHeightValue, 1};
        nodeAPI->setAttribute(node, NODE\_HEIGHT, &minHeightItem);
    }
    
    void AddGridItems(
        ArkUI\_NativeNodeAPI\_1\* nodeAPI,
        const std::shared\_ptr<ArkUIIrregularGridNode>& gridNode,
        const std::vector<GridItemSize>& itemSizes,
        const std::vector<uint32\_t>& colors)
    {
        for (size\_t i = 0; i < itemSizes.size(); ++i) {
            auto itemNode = nodeAPI->createNode(ARKUI\_NODE\_STACK);
            SetNodeColorAttribute(nodeAPI, itemNode, colors\[i % colors.size()\]);
            SetNodeBorderRadiusAttribute(nodeAPI, itemNode, GRID\_ITEM\_RADIUS);
            SetNodeBorderStyle(nodeAPI, itemNode);
            SetNodeHeightByRowSpan(nodeAPI, itemNode, itemSizes\[i\].first);
            gridNode->SetItemConfig(itemNode, itemSizes\[i\].first, itemSizes\[i\].second);
            nodeAPI->addChild(gridNode->GetHandle(), itemNode);
        }
    }
    } // namespace
    
    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);
    
        ArkUI\_NodeContentHandle contentHandle;
        OH\_ArkUI\_GetNodeContentFromNapiValue(env, args\[0\], &contentHandle);
        NativeEntry::GetInstance()->SetContentHandle(contentHandle);
    
        auto gridNode = std::make\_shared<ArkUIIrregularGridNode>();
        gridNode->SetBackgroundColor(GRID\_BACKGROUND\_COLOR);
        gridNode->SetColumnCount(GRID\_COLUMN\_COUNT);
        gridNode->SetGap(GRID\_GAP);
    
        auto\* nodeAPI = NativeModuleInstance::GetInstance()->GetNativeNodeAPI();
        AddGridItems(nodeAPI, gridNode, GetGridItemSizes(), GetGridItemColors());
    
        // 保持Native侧对象到管理类中，维护生命周期
        NativeEntry::GetInstance()->SetRootNode(gridNode);
        g\_env = env;
        return nullptr;
    }
    
    napi\_value DestroyNativeRoot(napi\_env env, napi\_callback\_info info)
    {
        // 从管理类中释放Native侧对象
        NativeEntry::GetInstance()->DisposeRootNode();
        return nullptr;
    }
    
    } // namespace NativeModule