【鸿蒙实战开发】ArkUI-构建自定义组件详解

201 篇文章 0 订阅
201 篇文章 2 订阅

前言

ArkUI开发框架在NDK接口提供了自定义UI组件的能力,这些能力包括自定义测算,自定义布局和自定义绘制。开发者通过注册相关自定义回调事件接入ArkUI开发框架的布局渲染流程,这些事件需要使用registerNodeCustomEvent来进行声明,并通过addNodeCustomEventReceiver函数添加组件自定义事件的监听器,在该监听器的回调函数中处理相关自定义测算,自定义布局和自定义绘制逻辑。

说明

  • 自定义组件事件注册需要addNodeCustomEventReceiver声明监听器注册和registerNodeCustomEvent声明需要的自定义事件类型,监听器只能监听已声明的事件。

  • 需要关注事件的反注册逻辑,如在组件销毁前调用removeNodeCustomEventReceiver移除事件监听器,unregisterNodeCustomEvent通知ArkUI框架已监听的自定义组件事件不再需要监听。

  • addNodeCustomEventReceiver可以添加多个函数指针,每个函数指针都会在对应事件触发时触发,对应的removeNodeCustomEventReceiver需要传递对应的函数指针用于移除监听。

  • registerNodeCustomEventReceiver是全局监听函数,不同于addNodeCustomEventReceiver registerNodeCustomEventReceiver能够监听所有Native组件的自定义事件触发,但只能传递一个函数指针,多次调用使用最后一次的函数指针进行回调,释放时使用unregisterNodeCustomEventReceiver进行反注册。

  • 自定义组件相关接口measureNode、layoutNode、setMeasuredSize、setLayoutPosition仅允许在对应的自定义事件(ARKUI_NODE_CUSTOM_EVENT_ON_MEASURE、ARKUI_NODE_CUSTOM_EVENT_ON_LAYOUT)回调中使用。

自定义布局容器

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

图1 自定义容器组件

image.png

  1. 按照接入ArkTS页面创建前置工程。

  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);
        OH_ArkUI_LayoutConstraint_SetMaxHeight(childLayoutConstrain, 1000);
        OH_ArkUI_LayoutConstraint_SetMaxWidth(childLayoutConstrain, 1000);
        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;
            }
        }
        // 自定义测算为所有子节点大小加固定边距。
        nativeModule_->setMeasuredSize(handle_, maxWidth + 2 * padding_, maxHeight + 2 * 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);
            // 布局子组件位置。
            nativeModule_->layoutNode(child, (selfSize.width - childSize.width) / 2,
                                      (selfSize.height - childSize.height) / 2);
        }
    }

    int32_t padding_ = 100;
};

} // namespace NativeModule

#endif // MYAPPLICATION_ARKUICUSTOMCONTAINERNODE_H
  1. 使用自定义容器创建带文本的示例界面,并沿用定时器模块相关简单实现。
// 自定义NDK接口入口。

#include "NativeEntry.h"

#include "ArkUICustomContainerNode.h"
#include "ArkUITextNode.h"

#include <arkui/native_node_napi.h>
#include <arkui/native_type.h>
#include <js_native_api.h>

namespace NativeModule {

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 textNode = std::make_shared<ArkUITextNode>();
    textNode->SetTextContent("CustomContainer Example");
    textNode->SetFontSize(16);
    textNode->SetBackgroundColor(0xFFfffacd);
    textNode->SetTextAlign(ARKUI_TEXT_ALIGNMENT_CENTER);
    node->AddChild(textNode);
    CreateNativeTimer(env, textNode.get(), 1, [](void *userData, int32_t count) {
        auto textNode = reinterpret_cast<ArkUITextNode *>(userData);
        textNode->SetCircleColor(0xFF00FF7F);
    });

    // 保持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

自定义绘制组件

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

图2 自定义绘制组件

image.png

  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();
        OH_Drawing_PathMoveTo(path, size.width / 4, size.height / 4);
        OH_Drawing_PathLineTo(path, size.width * 3 / 4, size.height / 4);
        OH_Drawing_PathLineTo(path, size.width * 3 / 4, size.height * 3 / 4);
        OH_Drawing_PathLineTo(path, size.width / 4, size.height * 3 / 4);
        OH_Drawing_PathLineTo(path, size.width / 4, size.height / 4);
        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
  1. 使用自定义绘制组件和自定义容器创建示例界面,并沿用定时器模块相关简单实现
// 自定义NDK接口入口组件。

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

namespace NativeModule {

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);
    customNode->SetWidth(150);
    customNode->SetHeight(150);
    node->AddChild(customNode);
    CreateNativeTimer(env, customNode.get(), 1, [](void *userData, int32_t count) {
        auto customNode = reinterpret_cast<ArkUICustomNode *>(userData);
        customNode->SetRectColor(0xFF00FF7F);
    });

    // 保持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

写在最后

●如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
●点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
●关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
●更多鸿蒙最新技术知识点,请移步前往小编:https://gitee.com/

在这里插入图片描述

  • 19
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值