监听组件布局与绘制送显事件
- 〇、前言
- 一、优化代码
-
- [1、弃用 share.h](#1、弃用 share.h)
- [2、新增 showUITextCallback](#2、新增 showUITextCallback)
- 3、更新代码
- 二、实现业务功能
-
- 1、准备舞台
- [2、完善 ArkUITextNode](#2、完善 ArkUITextNode)
-
- [2.1、OnLayoutCompleted 和 OnDrawCompleted](#2.1、OnLayoutCompleted 和 OnDrawCompleted)
- 2.2、为ArkUITextNode新增公共成员方法
- [3、更新 CreateTextListExample](#3、更新 CreateTextListExample)
- 4、真机体验
〇、前言
在app的运行过程中,页面布局是否完成,相关组件是否绘制送显,这些都是应用事件,使能够通过事件监听进行捕获的,从而完成特定的处理。在鸿蒙 NDK UI 中,可使用OH_ArkUI_RegisterLayoutCallbackOnNodeHandle 注册组件布局完成的回调方法。可使用OH_ArkUI_RegisterDrawCallbackOnNodeHandle 注册绘制送显完成的回调方法。可使用OH_ArkUI_UnregisterLayoutCallbackOnNodeHandle 取消组件布局完成的回调方法注册。可使用OH_ArkUI_UnregisterDrawCallbackOnNodeHandle取消绘制送显完成的回调方法注册。
下面,就基于这组关键 API 完成监听组件布局和绘制送显事件的自定义处理。
一、优化代码
在之前的几篇中,我为了让相关事件的捕获是否成功变得直观,都会使用类似下面的一段代码,去实现UI的动态更新:
cpp
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 文件加上原来的代码,就变成了下面这个模样:
cpp
#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 中的相关引用也需要同步更新:
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 方法:
cpp
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 命名空间之内,也就是如图的位置:

新增两个分别用于布局完成回调和绘制送显完成回调的方法,具体代码如下:
cpp
// 布局完成的回调方法
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新增一批与布局事件和绘制送显事件相关的公共成员方法:
cpp
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 模式外,通过查看日志的打印,也是能够看到布局完成的回调方法是有执行到的:
