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 自定义容器组件
-
按照 接入ArkTS页面 创建前置工程。
-
创建自定义容器组件封装对象。
// 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
- 使用自定义容器创建带文本的示例界面,并沿用 定时器模块相关简单实现 。
// 自定义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 自定义绘制组件
-
按照 自定义布局容器 章节准备前置工程。
-
创建自定义绘制组件封装对象。
// 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
- 使用自定义绘制组件和自定义容器创建示例界面,并沿用 定时器模块相关简单实现 。
// 自定义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
鸿蒙全栈开发全新学习指南
有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以要有一份实用的鸿蒙(HarmonyOS NEXT)学习路线与学习文档用来跟着学习是非常有必要的。
针对一些列因素,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、WebGL、元服务、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植等等)鸿蒙(HarmonyOS NEXT)技术知识点。
本路线共分为四个阶段:
第一阶段:鸿蒙初中级开发必备技能
第二阶段:鸿蒙南北双向高工技能基础:gitee.com/MNxiaona/733GH
第三阶段:应用开发中高级就业技术
第四阶段:全网首发-工业级南向设备开发就业技术:gitee.com/MNxiaona/733GH
《鸿蒙 (Harmony OS)开发学习手册》(共计892页)
如何快速入门?
1.基本概念
2.构建第一个ArkTS应用
3.……
开发基础知识:gitee.com/MNxiaona/733GH
1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……
基于ArkTS 开发
1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……
鸿蒙开发面试真题(含参考答案):gitee.com/MNxiaona/733GH
鸿蒙入门教学视频:
美团APP实战开发教学:gitee.com/MNxiaona/733GH
写在最后
- 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
- 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
- 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
- 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:
gitee.com/MNxiaona/733GH