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