代码改变世界

实用指南:鸿蒙原生系列之监听布局和送显事件

2025-12-26 19:55  tlnshuju  阅读(0)  评论(0)    收藏  举报

〇、前言

在app的运行过程中,页面布局是否完成,相关组件是否绘制送显,这些都是应用事件,使能够通过事件监听进行捕获的,从而完成特定的处理。在鸿蒙 NDK UI 中,可使用OH_ArkUI_RegisterLayoutCallbackOnNodeHandle注册组件布局完成的回调方法。可使用OH_ArkUI_RegisterDrawCallbackOnNodeHandle注册绘制送显完成的回调方法。可使用OH_ArkUI_UnregisterLayoutCallbackOnNodeHandle取消组件布局完成的回调方法注册。可使用OH_ArkUI_UnregisterDrawCallbackOnNodeHandle取消绘制送显完成的回调方法注册。

下面,就基于这组关键 API 完成监听组件布局和绘制送显事件的自定义处理。

一、优化代码

在之前的几篇中,我为了让相关事件的捕获是否成功变得直观,都会使用类似下面的一段代码,去实现UI的动态更新:

delete globalParams->value;
globalParams->value = new std::string("触发拖拽事件");
globalParams->desc = "触摸Image";
std::string data = globalParams->desc + ": " + *globalParams->value;
napi_env env = g_env; // 获取当前napi环境
napi_value callback;
napi_get_reference_value(env, g_clickCallbackRef, &callback);
// 构造传递参数
napi_value argv;
napi_create_string_utf8(env, data.c_str(), data.length(), &argv);
// 调用ArkTS回调
napi_value result;
napi_call_function(env, nullptr, callback, 1, &argv, &result);

考虑到不能每次想要往页面上打印点东西,就总是用上这么一大段几乎相同的代码,我决定降低代码的重复率,也就是将上面的一段代码抽取出来,封装成一个公共方法进行调用。

1、弃用 share.h

优化代码的第一个步骤,就是将之前的 share.h 给废弃了,将相关代码移到了 NativeModule.h 中,从而让工程中的相关方法具有一直的风格:都在 NativeModule 这个 namespace 中

NativeModule.h 文件加上原来的代码,就变成了下面这个模样:

#ifndef NATIVEPC_NATIVEMODULE_H
#define NATIVEPC_NATIVEMODULE_H
#include <arkui/native_node.h>
  #include <cassert>
    #include "napi/native_api.h"
    #include <string>
      #include <arkui/native_interface.h>
        #include <string>
          extern napi_env g_env;
          struct DynamicParams {
          std::string desc;
          std::string* value;
          };
          inline  DynamicParams* globalParams;
          // 全局保存ArkTS回调的napi_ref
          inline  napi_ref g_clickCallbackRef = nullptr;
          namespace NativeModule {
          static napi_value NativeInvokeUpdateEventInfo(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);
          // 保存ArkTS回调引用
          napi_create_reference(env, args[0], 1, &g_clickCallbackRef);
          if (globalParams && globalParams->value) {
          napi_value argv = nullptr;
          std::string data = globalParams->desc + ": " + *globalParams->value;
          //        std::string data = "测试";
          napi_status status = napi_create_string_utf8(env, data.c_str(), data.length(), &argv);
          if(status != napi_ok) {
          return nullptr;
          }
          napi_value result = nullptr;
          status = napi_call_function(env, nullptr, args[0], 1, &argv, &result);
          if (status != napi_ok) {
          return nullptr;
          }
          return result;
          } else {
          // 处理空指针异常或默认值情况
          napi_value argv = nullptr;
          std::string defaultStr = "default";
          napi_status status = napi_create_string_utf8(env, defaultStr.c_str(), defaultStr.length(), &argv);
          if(status != napi_ok) {
          return nullptr;
          }
          napi_value result = nullptr;
          status = napi_call_function(env, nullptr, args[0], 1, &argv, &result);
          if (status != napi_ok) {
          return nullptr;
          }
          return result;
          }
          }
          class NativeModuleInstance {
          public:
          static NativeModuleInstance *GetInstance() {
          static NativeModuleInstance instance;
          return &instance;
          }
          NativeModuleInstance() {
          // 获取NDK接口的函数指针结构体对象,用于后续操作。
          OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, arkUINativeNodeApi_);
          assert(arkUINativeNodeApi_);
          }
          // 暴露给其他模块使用。
          ArkUI_NativeNodeAPI_1 *GetNativeNodeAPI() { return arkUINativeNodeApi_; }
          private:
          ArkUI_NativeNodeAPI_1 *arkUINativeNodeApi_ = nullptr;
          };
          }
          #endif //NATIVEPC_NATIVEMODULE_H

由于 NativeInvokeUpdateEventInfo 方法的定义移动了位置,所以 napi_init.cpp 中的相关引用也需要同步更新:

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
g_env = env;
globalParams = new DynamicParams{"Default", new std::string("value")};
// 将 globalParams 封装为 napi_value(示例使用 external)
napi_value customData;
napi_create_external(env, globalParams, nullptr, nullptr, &customData);
napi_property_descriptor desc[] = {
{ "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr },
{"createNativeRoot", nullptr, NativeModule::CreateNativeRoot, nullptr, nullptr, nullptr, napi_default, nullptr},
{"destroyNativeRoot", nullptr, NativeModule::DestroyNativeRoot, nullptr, nullptr, nullptr, napi_default, nullptr},
{"updateEventInfo", nullptr, NativeModule::NativeInvokeUpdateEventInfo, nullptr, nullptr, customData, napi_default, nullptr}
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
EXTERN_C_END

2、新增 showUITextCallback

NativeEntry.cpp 中新增一个 showUITextCallback 方法:

void showUITextCallback(std::string tip, std::string content) {
globalParams->value = &content;
globalParams->desc = tip;
std::string data = globalParams->desc + ": " + *globalParams->value;
napi_env env = g_env; // 获取当前napi环境
napi_value callback;
napi_get_reference_value(env, g_clickCallbackRef, &callback);
// 构造传递参数
napi_value argv;
napi_create_string_utf8(env, data.c_str(), data.length(), &argv);
// 调用ArkTS回调
napi_value result;
napi_call_function(env, nullptr, callback, 1, &argv, &result);
}

这里,由于 globalParams 中的 value 不再是通过 new 创建的,而是通过 &content 指向了方法参数 content 相同的内存地址,所以,原来的 delete globalParams->value 这句代码就不能再继续使用了,否则就会引发应用崩溃;而这也是在C、C++ 这种具有直观的指针操作的编程语言中,所必需注意的地方

3、更新代码

将 showUITextCallback 方法封装好之后,之前那些用到 UI 更新的地方,都可以进行一番更新,就是用 showUITextCallback 方法去替换那一长段代码:
在这里插入图片描述

二、实现业务功能

接下来,回到本文正题,即实现组件布局和绘制送显事件的监听与捕获,并随之实现相应的自定义处理逻辑。

1、准备舞台

为了演示上面的目标功能,我们需要一个 UI 组件节点作为『舞台』,这里,我选中继续服用之前的列表组件,也就是需要将 NativeEntry.cpp 的 CreateNativeRoot 中创建文本列表的两行代码取消注释,并将上一篇中用到的图片节点的代码给注释掉。

在这里插入图片描述

2、完善 ArkUITextNode

为了实现目标功能,我们需要对 ArkUITextNode 类进行完善,补充一批事件相关的方法。

2.1、OnLayoutCompleted 和 OnDrawCompleted

首先,在 ArkUITextNode 类的定义体上方,NativeModule 命名空间之内,也就是如图的位置:
在这里插入图片描述
新增两个分别用于布局完成回调和绘制送显完成回调的方法,具体代码如下:

// 布局完成的回调方法
void OnLayoutCompleted(void* userData) {
showUITextCallback("监听布局事件", "布局完成回调");
ArkUI_NodeHandle node = (ArkUI_NodeHandle)userData;
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "the text_node is layout completed");
}
// 绘制送显完成的回调方法
void OnDrawCompleted(void* userData) {
showUITextCallback("监听绘制事件", "绘制送显完成");
ArkUI_NodeHandle node = (ArkUI_NodeHandle)userData;
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "the text_node is draw completed");
}

在这两个方法中,都用上了本文开头新封装的 showUITextCallback 方法。

2.2、为ArkUITextNode新增公共成员方法

接着,为ArkUITextNode新增一批与布局事件和绘制送显事件相关的公共成员方法:

void SetLayoutCallBack(int32_t nodeId) {
assert(handle_);
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "set layout callback");
// 注册布局完成的回调方法
OH_ArkUI_RegisterLayoutCallbackOnNodeHandle(handle_, this, OnLayoutCompleted);
}
void ResetLayoutCallBack() {
assert(handle_);
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "reset layout callback");
// 取消注册布局完成的回调方法
OH_ArkUI_UnregisterLayoutCallbackOnNodeHandle(handle_);
}
void SetDrawCallBack(int32_t nodeId) {
assert(handle_);
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "set draw callback");
// 注册绘制送显完成的回调方法
OH_ArkUI_RegisterDrawCallbackOnNodeHandle(handle_, this, OnDrawCompleted);
}
void ResetDrawCallBack() {
assert(handle_);
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "reset draw callback");
// 取消注册绘制送显完成的回调方法
OH_ArkUI_UnregisterDrawCallbackOnNodeHandle(handle_);
}
void SetInspectorId(std::string inspectorId) {
ArkUI_AttributeItem item = {nullptr, 0, inspectorId.c_str()};
nativeModule_->setAttribute(handle_, NODE_ID, &item);
}

3、更新 CreateTextListExample

最后,需要让『表演者』上『舞台』表演,也就是更新 CreateTextListExample 中的代码:
在这里插入图片描述

4、真机体验

对于布局完成的回调,从页面的文本显示来看,似乎没有成功捕获,但是,如果你进入 Debug 模式,就可以清晰的看到代码是被执行到的:
在这里插入图片描述
页面上之所以没有显示到,是因为这个事件很快就完成了,然后触发了绘制送显完成的回调方,于是页面上的文本就又被更新了。
除了使用 Debug 模式外,通过查看日志的打印,也是能够看到布局完成的回调方法是有执行到的:
在这里插入图片描述