鸿蒙原生系列之监听布局和送显事件

监听组件布局与绘制送显事件

〇、前言

在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 模式外,通过查看日志的打印,也是能够看到布局完成的回调方法是有执行到的:

相关推荐
YJlio29 分钟前
[鸿蒙2025领航者闯关] 基于鸿蒙 6 的「隐私感知跨设备办公助手」实战:星盾安全 + AI防窥 + 方舟引擎优化全流程复盘
人工智能·安全·harmonyos
御承扬29 分钟前
鸿蒙原生系列之ArkWeb技能提升——H5调用应用侧API
华为·harmonyos·arkweb·h5调试·h5调用应用方法
食品一少年32 分钟前
【Day7-10】开源鸿蒙Flutter 常用组件封装实战(2)
flutter·华为·harmonyos
赵财猫._.7 小时前
鸿蒙分布式安全通信:跨设备身份认证与数据加密传输
分布式·安全·harmonyos
Raink9 小时前
HarmonyOS应用开发基础案例(一):鸿蒙页面布局入门
harmonyos
Raink9 小时前
HarmonyOS 应用开发基础案例(三):使用DevEco Studio高效开发(篇一)
harmonyos
灰灰勇闯IT10 小时前
KMP算法在鸿蒙系统中的应用:从字符串匹配到高效系统级开发(附实战代码)
算法·华为·harmonyos
灰灰勇闯IT11 小时前
Flutter×鸿蒙深度融合指南:从跨端适配到分布式能力落地(2025最新实战)
分布式·flutter·harmonyos
遇到困难睡大觉哈哈14 小时前
Harmonny os——《从 TypeScript 到 ArkTS 的适配规则》精简笔记
笔记·typescript·harmonyos·鸿蒙