1# 构建自定义组件 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @xiang-shouxing--> 5<!--Designer: @xiang-shouxing--> 6<!--Tester: @sally__--> 7<!--Adviser: @HelloCrease--> 8 9ArkUI开发框架在NDK接口提供了自定义UI组件的能力,这些能力包括自定义测算,自定义布局和自定义绘制。开发者通过注册相关自定义回调事件接入ArkUI开发框架的布局渲染流程,这些事件需要使用[registerNodeCustomEvent](../reference/apis-arkui/capi-arkui-nativemodule-arkui-nativenodeapi-1.md#registernodecustomevent)来进行声明,并通过[addNodeCustomEventReceiver](../reference/apis-arkui/capi-arkui-nativemodule-arkui-nativenodeapi-1.md#addnodecustomeventreceiver)函数添加组件自定义事件的监听器,在该监听器的回调函数中处理相关自定义测算,自定义布局和自定义绘制逻辑。 10 11 12> **说明:** 13> 14> - 自定义组件事件注册需要[addNodeCustomEventReceiver](../reference/apis-arkui/capi-arkui-nativemodule-arkui-nativenodeapi-1.md#addnodecustomeventreceiver)声明监听器注册和[registerNodeCustomEvent](../reference/apis-arkui/capi-arkui-nativemodule-arkui-nativenodeapi-1.md#registernodecustomevent)声明需要的自定义事件类型,监听器只能监听已声明的事件。 15> 16> - 需要关注事件的反注册逻辑,如在组件销毁前调用[removeNodeCustomEventReceiver](../reference/apis-arkui/capi-arkui-nativemodule-arkui-nativenodeapi-1.md#removenodecustomeventreceiver)移除事件监听器,[unregisterNodeCustomEvent](../reference/apis-arkui/capi-arkui-nativemodule-arkui-nativenodeapi-1.md#unregisternodecustomevent)通知ArkUI框架已监听的自定义组件事件不再需要监听。 17> 18> - [addNodeCustomEventReceiver](../reference/apis-arkui/capi-arkui-nativemodule-arkui-nativenodeapi-1.md#addnodecustomeventreceiver)可以添加多个函数指针,每个函数指针都会在对应事件触发时触发,对应的[removeNodeCustomEventReceiver](../reference/apis-arkui/capi-arkui-nativemodule-arkui-nativenodeapi-1.md#removenodecustomeventreceiver)需要传递对应的函数指针用于移除监听。 19> 20> - [registerNodeCustomEventReceiver](../reference/apis-arkui/capi-arkui-nativemodule-arkui-nativenodeapi-1.md#registernodecustomeventreceiver)是全局监听函数,不同于[addNodeCustomEventReceiver](../reference/apis-arkui/capi-arkui-nativemodule-arkui-nativenodeapi-1.md#addnodecustomeventreceiver),[registerNodeCustomEventReceiver](../reference/apis-arkui/capi-arkui-nativemodule-arkui-nativenodeapi-1.md#registernodecustomeventreceiver)能够监听所有Native组件的自定义事件触发,但只能传递一个函数指针,多次调用使用最后一次的函数指针进行回调,释放时使用unregisterNodeCustomEventReceiver进行反注册。 21> 22> - 自定义组件相关接口([measureNode](../reference/apis-arkui/capi-arkui-nativemodule-arkui-nativenodeapi-1.md#measurenode)、[layoutNode](../reference/apis-arkui/capi-arkui-nativemodule-arkui-nativenodeapi-1.md#layoutnode)、[setMeasuredSize](../reference/apis-arkui/capi-arkui-nativemodule-arkui-nativenodeapi-1.md#setmeasuredsize)、[setLayoutPosition](../reference/apis-arkui/capi-arkui-nativemodule-arkui-nativenodeapi-1.md#setlayoutposition))仅允许在对应的自定义事件([ARKUI_NODE_CUSTOM_EVENT_ON_MEASURE、ARKUI_NODE_CUSTOM_EVENT_ON_LAYOUT](../reference/apis-arkui/capi-native-node-h.md#arkui_nodecustomeventtype))回调中使用。 23 24 25## 自定义布局容器 26 27以下示例创建了一个自定义容器,该容器将子组件最大值加上额外边距作为自身大小,同时对子组件进行居中排布。 28 29**图1** 自定义容器组件 30 31 32 331. 按照[接入ArkTS页面](ndk-access-the-arkts-page.md)创建前置工程。 34 352. 创建自定义容器组件封装对象。 36 ```c 37 // ArkUICustomContainerNode.h 38 // 自定义容器组件示例 39 40 #ifndef MYAPPLICATION_ARKUICUSTOMCONTAINERNODE_H 41 #define MYAPPLICATION_ARKUICUSTOMCONTAINERNODE_H 42 43 #include "ArkUINode.h" 44 45 namespace NativeModule { 46 47 class ArkUICustomContainerNode : public ArkUINode { 48 public: 49 // 使用自定义组件类型ARKUI_NODE_CUSTOM创建组件。 50 ArkUICustomContainerNode() 51 : ArkUINode((NativeModuleInstance::GetInstance()->GetNativeNodeAPI())->createNode(ARKUI_NODE_CUSTOM)) { 52 // 注册自定义事件监听器。 53 nativeModule_->addNodeCustomEventReceiver(handle_, OnStaticCustomEvent); 54 // 声明自定义事件并传递自身作为自定义数据。 55 nativeModule_->registerNodeCustomEvent(handle_, ARKUI_NODE_CUSTOM_EVENT_ON_MEASURE, 0, this); 56 nativeModule_->registerNodeCustomEvent(handle_, ARKUI_NODE_CUSTOM_EVENT_ON_LAYOUT, 0, this); 57 } 58 59 ~ArkUICustomContainerNode() override { 60 // 反注册自定义事件监听器。 61 nativeModule_->removeNodeCustomEventReceiver(handle_, OnStaticCustomEvent); 62 // 取消声明自定义事件。 63 nativeModule_->unregisterNodeCustomEvent(handle_, ARKUI_NODE_CUSTOM_EVENT_ON_MEASURE); 64 nativeModule_->unregisterNodeCustomEvent(handle_, ARKUI_NODE_CUSTOM_EVENT_ON_LAYOUT); 65 } 66 67 void SetPadding(int32_t padding) { 68 padding_ = padding; 69 // 自定义属性事件更新需要主动调用标记脏区接口。 70 nativeModule_->markDirty(handle_, NODE_NEED_MEASURE); 71 } 72 73 private: 74 static void OnStaticCustomEvent(ArkUI_NodeCustomEvent *event) { 75 // 获取组件实例对象,调用相关实例方法。 76 auto customNode = reinterpret_cast<ArkUICustomContainerNode *>(OH_ArkUI_NodeCustomEvent_GetUserData(event)); 77 auto type = OH_ArkUI_NodeCustomEvent_GetEventType(event); 78 switch (type) { 79 case ARKUI_NODE_CUSTOM_EVENT_ON_MEASURE: 80 customNode->OnMeasure(event); 81 break; 82 case ARKUI_NODE_CUSTOM_EVENT_ON_LAYOUT: 83 customNode->OnLayout(event); 84 break; 85 default: 86 break; 87 } 88 } 89 90 // 自定义测算逻辑。 91 void OnMeasure(ArkUI_NodeCustomEvent *event) { 92 auto layoutConstrain = OH_ArkUI_NodeCustomEvent_GetLayoutConstraintInMeasure(event); 93 // 创建子节点布局限制,复用父组件布局中的百分比参考值。 94 auto childLayoutConstrain = OH_ArkUI_LayoutConstraint_Copy(layoutConstrain); 95 OH_ArkUI_LayoutConstraint_SetMaxHeight(childLayoutConstrain, 1000); 96 OH_ArkUI_LayoutConstraint_SetMaxWidth(childLayoutConstrain, 1000); 97 OH_ArkUI_LayoutConstraint_SetMinHeight(childLayoutConstrain, 0); 98 OH_ArkUI_LayoutConstraint_SetMinWidth(childLayoutConstrain, 0); 99 100 // 测算子节点获取子节点最大值。 101 auto totalSize = nativeModule_->getTotalChildCount(handle_); 102 int32_t maxWidth = 0; 103 int32_t maxHeight = 0; 104 for (uint32_t i = 0; i < totalSize; i++) { 105 auto child = nativeModule_->getChildAt(handle_, i); 106 // 调用测算接口测算Native组件。 107 nativeModule_->measureNode(child, childLayoutConstrain); 108 auto size = nativeModule_->getMeasuredSize(child); 109 if (size.width > maxWidth) { 110 maxWidth = size.width; 111 } 112 if (size.height > maxHeight) { 113 maxHeight = size.height; 114 } 115 } 116 // 自定义测算为所有子节点大小加固定边距。该自定义节点最终的尺寸以此处设置的值为准。 117 nativeModule_->setMeasuredSize(handle_, maxWidth + 2 * padding_, maxHeight + 2 * padding_); 118 } 119 120 void OnLayout(ArkUI_NodeCustomEvent *event) { 121 // 获取父组件期望位置并设置。 122 auto position = OH_ArkUI_NodeCustomEvent_GetPositionInLayout(event); 123 nativeModule_->setLayoutPosition(handle_, position.x, position.y); 124 125 // 设置子组件居中对齐。 126 auto totalSize = nativeModule_->getTotalChildCount(handle_); 127 auto selfSize = nativeModule_->getMeasuredSize(handle_); 128 for (uint32_t i = 0; i < totalSize; i++) { 129 auto child = nativeModule_->getChildAt(handle_, i); 130 // 获取子组件大小。 131 auto childSize = nativeModule_->getMeasuredSize(child); 132 // 布局子组件位置。 133 nativeModule_->layoutNode(child, (selfSize.width - childSize.width) / 2, 134 (selfSize.height - childSize.height) / 2); 135 } 136 } 137 138 int32_t padding_ = 100; 139 }; 140 141 } // namespace NativeModule 142 143 #endif // MYAPPLICATION_ARKUICUSTOMCONTAINERNODE_H 144 ``` 145 1463. 使用自定义容器创建带文本的示例界面,并沿用[定时器模块相关简单实现](ndk-embed-arkts-components.md)。 147 ```c 148 // 自定义NDK接口入口。 149 150 #include "NativeEntry.h" 151 152 #include "ArkUICustomContainerNode.h" 153 #include "ArkUITextNode.h" 154 155 #include <arkui/native_node_napi.h> 156 #include <arkui/native_type.h> 157 #include <js_native_api.h> 158 159 namespace NativeModule { 160 namespace { 161 napi_env g_env; 162 } // namespace 163 164 napi_value CreateNativeRoot(napi_env env, napi_callback_info info) { 165 size_t argc = 1; 166 napi_value args[1] = {nullptr}; 167 168 napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); 169 170 // 获取NodeContent 171 ArkUI_NodeContentHandle contentHandle; 172 OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle); 173 NativeEntry::GetInstance()->SetContentHandle(contentHandle); 174 175 // 创建自定义容器和文本组件。 176 auto node = std::make_shared<ArkUICustomContainerNode>(); 177 node->SetBackgroundColor(0xFFE0FFFF); 178 auto textNode = std::make_shared<ArkUITextNode>(); 179 textNode->SetTextContent("CustomContainer Example"); 180 textNode->SetFontSize(16); 181 textNode->SetBackgroundColor(0xFFfffacd); 182 textNode->SetTextAlign(ARKUI_TEXT_ALIGNMENT_CENTER); 183 node->AddChild(textNode); 184 CreateNativeTimer(env, textNode.get(), 1, [](void *userData, int32_t count) { 185 auto textNode = reinterpret_cast<ArkUITextNode *>(userData); 186 textNode->SetFontColor(0xFF00FF7F); 187 }); 188 189 // 保持Native侧对象到管理类中,维护生命周期。 190 NativeEntry::GetInstance()->SetRootNode(node); 191 g_env = env; 192 return nullptr; 193 } 194 195 napi_value DestroyNativeRoot(napi_env env, napi_callback_info info) { 196 // 从管理类中释放Native侧对象。 197 NativeEntry::GetInstance()->DisposeRootNode(); 198 return nullptr; 199 } 200 201 } // namespace NativeModule 202 ``` 203 204 205## 自定义绘制组件 206 207以下示例创建了一个自定义绘制组件,该绘制组件能够绘制自定义矩形,并使用上述自定义容器进行布局排布。 208 209**图2** 自定义绘制组件 210 211 212 2131. 按照[自定义布局容器](#自定义布局容器)章节准备前置工程。 214 2152. 创建自定义绘制组件封装对象。 216 ```c 217 // ArkUICustomNode.h 218 // 自定义绘制组件示例 219 220 #ifndef MYAPPLICATION_ARKUICUSTOMNODE_H 221 #define MYAPPLICATION_ARKUICUSTOMNODE_H 222 223 #include <native_drawing/drawing_brush.h> 224 #include <native_drawing/drawing_canvas.h> 225 #include <native_drawing/drawing_path.h> 226 227 #include "ArkUINode.h" 228 229 namespace NativeModule { 230 231 class ArkUICustomNode : public ArkUINode { 232 public: 233 // 使用自定义组件类型ARKUI_NODE_CUSTOM创建组件。 234 ArkUICustomNode() 235 : ArkUINode((NativeModuleInstance::GetInstance()->GetNativeNodeAPI())->createNode(ARKUI_NODE_CUSTOM)) { 236 // 注册自定义事件监听器。 237 nativeModule_->addNodeCustomEventReceiver(handle_, OnStaticCustomEvent); 238 // 声明自定义事件并传递自身作为自定义数据。 239 nativeModule_->registerNodeCustomEvent(handle_, ARKUI_NODE_CUSTOM_EVENT_ON_DRAW, 0, this); 240 } 241 242 ~ArkUICustomNode() override { 243 // 反注册自定义事件监听器。 244 nativeModule_->removeNodeCustomEventReceiver(handle_, OnStaticCustomEvent); 245 // 取消声明自定义事件。 246 nativeModule_->unregisterNodeCustomEvent(handle_, ARKUI_NODE_CUSTOM_EVENT_ON_DRAW); 247 } 248 249 void SetRectColor(uint32_t color) { 250 color_ = color; 251 // 自定义绘制属性变更需要主动通知框架。 252 nativeModule_->markDirty(handle_, NODE_NEED_RENDER); 253 } 254 255 private: 256 static void OnStaticCustomEvent(ArkUI_NodeCustomEvent *event) { 257 // 获取组件实例对象,调用相关实例方法。 258 auto customNode = reinterpret_cast<ArkUICustomNode *>(OH_ArkUI_NodeCustomEvent_GetUserData(event)); 259 auto type = OH_ArkUI_NodeCustomEvent_GetEventType(event); 260 switch (type) { 261 case ARKUI_NODE_CUSTOM_EVENT_ON_DRAW: 262 customNode->OnDraw(event); 263 break; 264 default: 265 break; 266 } 267 } 268 269 // 自定义绘制逻辑。 270 void OnDraw(ArkUI_NodeCustomEvent *event) { 271 auto drawContext = OH_ArkUI_NodeCustomEvent_GetDrawContextInDraw(event); 272 // 获取图形绘制对象。 273 auto drawCanvas = reinterpret_cast<OH_Drawing_Canvas *>(OH_ArkUI_DrawContext_GetCanvas(drawContext)); 274 // 获取组件大小。 275 auto size = OH_ArkUI_DrawContext_GetSize(drawContext); 276 // 绘制自定义内容。 277 auto path = OH_Drawing_PathCreate(); 278 OH_Drawing_PathMoveTo(path, size.width / 4, size.height / 4); 279 OH_Drawing_PathLineTo(path, size.width * 3 / 4, size.height / 4); 280 OH_Drawing_PathLineTo(path, size.width * 3 / 4, size.height * 3 / 4); 281 OH_Drawing_PathLineTo(path, size.width / 4, size.height * 3 / 4); 282 OH_Drawing_PathLineTo(path, size.width / 4, size.height / 4); 283 OH_Drawing_PathClose(path); 284 auto brush = OH_Drawing_BrushCreate(); 285 OH_Drawing_BrushSetColor(brush, color_); 286 OH_Drawing_CanvasAttachBrush(drawCanvas, brush); 287 OH_Drawing_CanvasDrawPath(drawCanvas, path); 288 // 释放资源 289 OH_Drawing_BrushDestroy(brush); 290 OH_Drawing_PathDestroy(path); 291 } 292 293 uint32_t color_ = 0xFFFFE4B5; 294 }; 295 296 } // namespace NativeModule 297 298 #endif // MYAPPLICATION_ARKUICUSTOMNODE_H 299 ``` 300 3013. 使用自定义绘制组件和自定义容器创建示例界面,并沿用[定时器模块相关简单实现](ndk-embed-arkts-components.md)。 302 ```c 303 // 自定义NDK接口入口组件。 304 305 #include "NativeEntry.h" 306 307 #include "ArkUICustomContainerNode.h" 308 #include "ArkUICustomNode.h" 309 310 #include <arkui/native_node_napi.h> 311 #include <arkui/native_type.h> 312 #include <js_native_api.h> 313 314 namespace NativeModule { 315 namespace { 316 napi_env g_env; 317 } // namespace 318 319 napi_value CreateNativeRoot(napi_env env, napi_callback_info info) { 320 size_t argc = 1; 321 napi_value args[1] = {nullptr}; 322 323 napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); 324 325 // 获取NodeContent 326 ArkUI_NodeContentHandle contentHandle; 327 OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle); 328 NativeEntry::GetInstance()->SetContentHandle(contentHandle); 329 330 // 创建自定义容器和自定义绘制组件。 331 auto node = std::make_shared<ArkUICustomContainerNode>(); 332 node->SetBackgroundColor(0xFFE0FFFF); 333 auto customNode = std::make_shared<ArkUICustomNode>(); 334 customNode->SetBackgroundColor(0xFFD3D3D3); 335 customNode->SetWidth(150); 336 customNode->SetHeight(150); 337 node->AddChild(customNode); 338 CreateNativeTimer(env, customNode.get(), 1, [](void *userData, int32_t count) { 339 auto customNode = reinterpret_cast<ArkUICustomNode *>(userData); 340 customNode->SetRectColor(0xFF00FF7F); 341 }); 342 343 // 保持Native侧对象到管理类中,维护生命周期。 344 NativeEntry::GetInstance()->SetRootNode(node); 345 g_env = env; 346 return nullptr; 347 } 348 349 napi_value DestroyNativeRoot(napi_env env, napi_callback_info info) { 350 // 从管理类中释放Native侧对象。 351 NativeEntry::GetInstance()->DisposeRootNode(); 352 return nullptr; 353 } 354 355 } // namespace NativeModule 356 357 ``` 358