【鸿蒙基于API 13实战开发】—— ArkUI 组件(自定义渲染 (XComponent))


📚鸿蒙开发往期学习笔录✒️:

✒️ 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~
✒️ 鸿蒙应用开发与鸿蒙系统开发哪个更有前景?
✒️ 嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~
✒️ 对于大前端开发来说,转鸿蒙开发究竟是福还是祸?
✒️ 鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?
✒️ 记录一场鸿蒙开发岗位面试经历~
✒️ 持续更新中……


概述

XComponent组件作为一种渲染组件,可用于EGL/OpenGLES和媒体数据写入,通过使用XComponent独有的“ NativeWindow ”来渲染画面,通常用于满足开发者较为复杂的自定义渲染需求,例如相机预览流的显示和游戏画面的渲染。其可通过指定type字段来实现不同的渲染方式,分别为 XComponentType .SURFACE和XComponentType.TEXTURE。对于SURFACE类型,开发者将定制的绘制内容单独展示到屏幕上。对于TEXTURE类型,开发者将定制的绘制内容和XComponent组件的内容合成后展示到屏幕上。

目前XComponent组件主要有两个应用场景。一个是Native XComponent场景,在native层获取Native XComponent实例,在native侧注册XComponent的生命周期回调,以及触摸、鼠标、按键等事件回调。另一个是ArkTS XComponent场景,在ArkTS侧获取SurfaceId,生命周期回调、触摸、鼠标、按键等事件回调等均在ArkTS侧触发。

Native XComponent场景

在XComponent组件构造函数的libraryname中定义需要加载的动态库,而后应用就可以在Native层获取Native XComponent实例,其是XComponent组件提供在Native层的实例,可作为ArkTS层和Native层XComponent绑定的桥梁。XComponent所提供的NDK接口都依赖于该实例。接口能力包括获取NativeWindow实例、获取XComponent的布局/事件信息、注册XComponent的生命周期回调、注册XComponent的触摸、鼠标、按键等事件回调。针对Native XComponent,主要的开发场景如下:

  • 利用Native XComponent提供的接口注册XComponent的生命周期和事件回调。
  • 在这些回调中进行初始化环境、获取当前状态、响应各类事件的开发。
  • 利用NativeWindow和EGL接口开发自定义绘制内容以及申请和提交Buffer到图形队列。

接口说明

接口名 描述
OH_NativeXComponent_GetXComponentId(OH_NativeXComponent* component, char* id, uint64_t* size) 获取XComponent的id。
OH_NativeXComponent_GetXComponentSize(OH_NativeXComponent* component, const void* window, uint64_t* width, uint64_t* height) 获取XComponent持有的surface的大小。
OH_NativeXComponent_GetXComponentOffset(OH_NativeXComponent* component, const void* window, double* x, double* y) 获取XComponent持有的surface相对其父组件左顶点的偏移量。
OH_NativeXComponent_GetTouchEvent(OH_NativeXComponent* component, const void* window, OH_NativeXComponent_TouchEvent* touchEvent) 获取由XComponent触发的触摸事件。touchEvent内的具体属性值可参考 OH_NativeXComponent_TouchEvent 。
OH_NativeXComponent_GetTouchPointToolType(OH_NativeXComponent* component, uint32_t pointIndex, OH_NativeXComponent_TouchPointToolType* toolType) 获取XComponent触摸点的工具类型。
OH_NativeXComponent_GetTouchPointTiltX(OH_NativeXComponent* component, uint32_t pointIndex, float* tiltX) 获取XComponent触摸点处相对X轴的倾斜角度。
OH_NativeXComponent_GetTouchPointTiltY(OH_NativeXComponent* component, uint32_t pointIndex, float* tiltY) 获取XComponent触摸点处相对Y轴的倾斜角度。
OH_NativeXComponent_GetMouseEvent(OH_NativeXComponent* component, const void* window, OH_NativeXComponent_MouseEvent* mouseEvent) 获取由XComponent触发的鼠标事件。
OH_NativeXComponent_RegisterCallback(OH_NativeXComponent* component, OH_NativeXComponent_Callback* callback) 为此OH_NativeXComponent实例注册生命周期和触摸事件回调。
OH_NativeXComponent_RegisterMouseEventCallback(OH_NativeXComponent* component, OH_NativeXComponent_MouseEvent_Callback* callback) 为此OH_NativeXComponent实例注册鼠标事件回调。
OH_NativeXComponent_RegisterFocusEventCallback(OH_NativeXComponent* component, void (callback)(OH_NativeXComponent component, void* window)) 为此OH_NativeXComponent实例注册获得焦点事件回调。
OH_NativeXComponent_RegisterKeyEventCallback(OH_NativeXComponent* component, void (callback)(OH_NativeXComponent component, void* window)) 为此OH_NativeXComponent实例注册按键事件回调。
OH_NativeXComponent_RegisterBlurEventCallback(OH_NativeXComponent* component, void (callback)(OH_NativeXComponent component, void* window)) 为此OH_NativeXComponent实例注册失去焦点事件回调。
OH_NativeXComponent_GetKeyEvent(OH_NativeXComponent* component, OH_NativeXComponent_KeyEvent** keyEvent) 获取由XComponent触发的按键事件。
OH_NativeXComponent_GetKeyEventAction(OH_NativeXComponent_KeyEvent* keyEvent, OH_NativeXComponent_KeyAction* action) 获取按键事件的动作。
OH_NativeXComponent_GetKeyEventCode(OH_NativeXComponent_KeyEvent* keyEvent, OH_NativeXComponent_KeyCode* code) 获取按键事件的键码值。
OH_NativeXComponent_GetKeyEventSourceType(OH_NativeXComponent_KeyEvent* keyEvent, OH_NativeXComponent_EventSourceType* sourceType) 获取按键事件的输入源类型。
OH_NativeXComponent_GetKeyEventDeviceId(OH_NativeXComponent_KeyEvent* keyEvent, int64_t* deviceId) 获取按键事件的设备ID。
OH_NativeXComponent_GetKeyEventTimestamp(OH_NativeXComponent_KeyEvent* keyEvent, int64_t* timestamp) 获取按键事件的时间戳。

说明
上述接口不支持跨线程访问。

XComponent销毁(onSurfaceDestroyed回调触发后)时会释放上述接口中获取的OH_NativeXComponent和window对象。如果在之后再次使用获取的这些对象有可能会导致出现使用野指针或空指针的崩溃问题,需要开发者避免。

开发步骤

以下步骤以SURFACE类型为例,描述了如何使用XComponent组件调用Node-API接口来创建EGL/GLES环境,实现在主页面绘制图形,并可以改变图形的颜色。

  1. 在界面中定义XComponent。
//接口声明
export default interface XComponentContext {
  drawPattern(): void;

  getStatus(): XComponentContextStatus;
};

type XComponentContextStatus = {
  hasDraw: boolean,
  hasChangeColor: boolean,
};

@Entry
@Component
struct Index {
    @State message: string = 'Hello World'
    xComponentContext: object | undefined = undefined;
    xComponentAttrs: XComponentAttrs = {
        id: 'xcomponentId',
        type: XComponentType.SURFACE,
        libraryname: 'nativerender'
    }

    build() {
    Row() {
        // ...
        // 在xxx.ets 中定义 XComponent
        XComponent(this.xComponentAttrs)
            .focusable(true) // 可响应键盘事件
            .onLoad((xComponentContext) => {
            this.xComponentContext = xComponentContext;
            })
            .onDestroy(() => {
            console.log("onDestroy");
            })
        // ...
        }
        .height('100%')
    }
}
    
interface XComponentAttrs {
id: string;
    type: number;
    libraryname: string;
}
  1. Node-API模块注册,具体使用请参考 Native API在应用工程中的使用指导 。
// 在napi_init.cpp文件中,Init方法注册接口函数,从而将封装的C++方法传递出来,供ArkTS侧调用
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    // ...
    // 向ArkTS侧暴露接口getContext()
    napi_property_descriptor desc[] = {
        { "getContext", nullptr, PluginManager::GetContext, nullptr, nullptr, nullptr, napi_default, nullptr }
    };
    if (napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc) != napi_ok) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init", "napi_define_properties failed");
        return nullptr;
    }
    // 方法内检查环境变量是否包含XComponent组件实例,若实例存在注册绘制相关接口
    PluginManager::GetInstance()->Export(env, exports);
    return exports;
}
EXTERN_C_END

// 编写接口的描述信息,根据实际需要可以修改对应参数
static napi_module nativerenderModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    // 入口函数
    .nm_register_func = Init,// 指定加载对应模块时的回调函数
    // 模块名称
    .nm_modname = "nativerender", // 指定模块名称,对于XComponent相关开发,这个名称必须和ArkTS侧XComponent中libraryname的值保持一致
    .nm_priv = ((void *)0),
    .reserved = { 0 }
};

// __attribute__((constructor))修饰的方法由系统自动调用,使用Node-API接口napi_module_register()传入模块描述信息进行模块注册
extern "C" __attribute__((constructor)) void RegisterModule(void)
{
    napi_module_register(&nativerenderModule);
}

// 使用Node-API中的napi_define_properties方法,向ArkTS侧暴露drawPattern()方法,在ArkTS侧调用drawPattern()来绘制内容。
void PluginRender::Export(napi_env env, napi_value exports)
{
    // ...
    // 将接口函数注册为ArkTS侧接口drawPattern
    napi_property_descriptor desc[] = {
        { "drawPattern", nullptr, PluginRender::NapiDrawPattern, nullptr, nullptr, nullptr, napi_default, nullptr }
    };
    if (napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc) != napi_ok) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginRender", "Export: napi_define_properties failed");
    }
}
  1. 注册XComponent事件回调,使用Node-API实现XComponent事件回调函数。
    (1) 定义surface创建成功,发生改变,销毁和XComponent的touch事件回调接口。
//定义PluginRender类
class PluginRender {
public:
    explicit PluginRender(std::string& id);
    ~PluginRender()
    {
        if (eglCore_ != nullptr) {
            eglCore_->Release();
            delete eglCore_;
            eglCore_ = nullptr;
        }
    }
    static PluginRender* GetInstance(std::string& id);
    static void Release(std::string& id);
    static napi_value NapiDrawPattern(napi_env env, napi_callback_info info);
    void Export(napi_env env, napi_value exports);
    void OnSurfaceChanged(OH_NativeXComponent* component, void* window);
    void OnTouchEvent(OH_NativeXComponent* component, void* window);
    void OnMouseEvent(OH_NativeXComponent* component, void* window);
    void OnHoverEvent(OH_NativeXComponent* component, bool isHover);
    void OnFocusEvent(OH_NativeXComponent* component, void* window);
    void OnBlurEvent(OH_NativeXComponent* component, void* window);
    void OnKeyEvent(OH_NativeXComponent* component, void* window);
    void RegisterCallback(OH_NativeXComponent* nativeXComponent);

public:
    static std::unordered_map<std::string, PluginRender*> instance_;
    EGLCore* eglCore_;
    static int32_t hasDraw_;
    static int32_t hasChangeColor_;

private:
    OH_NativeXComponent_Callback renderCallback_;
    OH_NativeXComponent_MouseEvent_Callback mouseCallback_;
};

std::unordered_map<std::string, PluginRender*> PluginRender::instance_;
int32_t PluginRender::hasDraw_ = 0;
int32_t PluginRender::hasChangeColor_ = 0;

PluginRender* PluginRender::GetInstance(std::string& id)
{
    if (instance_.find(id) == instance_.end()) {
        PluginRender* instance = new PluginRender(id);
        instance_[id] = instance;
        return instance;
    } else {
        return instance_[id];
    }
}

// 定义一个函数OnSurfaceCreatedCB(),封装初始化环境与绘制背景
void OnSurfaceCreatedCB(OH_NativeXComponent *component, void *window)
{
    // ...
    // 获取XComponent的id,即ArkTS侧XComponent组件构造中的id参数
    char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = { '\0' };
    uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1;
    if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != OH_NativeXComponent_RESULT_SUCCESS) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback",
            "OnSurfaceCreatedCB: Unable to get XComponent id");
        return;
    }

    // 初始化环境与绘制背景
    std::string id(idStr);
    auto render = PluginRender::GetInstance(id);
    uint64_t width;
    uint64_t height;
    // 获取XComponent拥有的surface的大小
    int32_t xSize = OH_NativeXComponent_GetXComponentSize(component, window, &width, &height);
    if ((xSize == OH_NativeXComponent_RESULT_SUCCESS) && (render != nullptr)) {
        if (render->eglCore_->EglContextInit(window, width, height)) {
            render->eglCore_->Background();
        }
    }
}

// 定义一个函数OnSurfaceChangedCB()
void OnSurfaceChangedCB(OH_NativeXComponent *component, void *window)
{
    // ...
    // 获取XComponent的id
    char idStr[OH_
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值