XComponent(繪制)

2024-01-25 13:17 更新

XComponent組件作為一種繪制組件,通常用于滿足開發(fā)者較為復雜的自定義繪制需求,例如相機預覽流的顯示和游戲畫面的繪制。

其可通過指定其type字段來實現(xiàn)不同的功能,主要有兩個“surface”和“component”字段可供選擇。

對于“surface”類型,開發(fā)者可將相關數(shù)據(jù)傳入XComponent單獨擁有的“NativeWindow”來渲染畫面。

對于“component”類型則主要用于實現(xiàn)動態(tài)加載顯示內容的目的。

surface類型

XComponent設置為surface類型時通常用于EGL/OpenGLES和媒體數(shù)據(jù)寫入,并將其顯示在XComponent組件上。

設置為“surface“類型時XComponent組件可以和其他組件一起進行布局和渲染。

同時XComponent又擁有單獨的“NativeWindow“,可以為開發(fā)者在native側提供native window用來創(chuàng)建EGL/OpenGLES環(huán)境,進而使用標準的OpenGL ES開發(fā)。

除此之外,媒體相關應用(視頻、相機等)也可以將相關數(shù)據(jù)寫入XComponent所提供的NativeWindow,從而實現(xiàn)呈現(xiàn)相應畫面。

使用EGL/OpenGLES渲染

native側代碼開發(fā)要點

HarmonyOS的應用如果要通過js來橋接native,一般需要使用napi接口來處理js交互,XComponent同樣不例外,具體使用請參考Native API在應用工程中的使用指導

Native側處理js邏輯的文件類型為so:

  • 每個模塊對應一個so
  • so的命名規(guī)則為 lib{模塊名}.so

對于使用XComponent進行標準OpenGL ES開發(fā)的場景,CMAKELists.txt文件內容大致如下:

  1. cmake_minimum_required(VERSION 3.4.1)
  2. project(XComponent) # 項目名稱
  3. set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
  4. # 頭文件查找路徑
  5. include_directories(${NATIVERENDER_ROOT_PATH}
  6. ${NATIVERENDER_ROOT_PATH}/include
  7. )
  8. # 編譯目標so,SHARED表示動態(tài)庫
  9. add_library(nativerender SHARED
  10. xxx.cpp
  11. )
  12. # 查找相關庫 (包括OpenGL ES相關庫和XComponent提供的ndk接口)
  13. find_library( EGL-lib
  14. EGL )
  15. find_library( GLES-lib
  16. GLESv3 )
  17. find_library( libace-lib
  18. ace_ndk.z )
  19. # 編譯so所需要的依賴
  20. target_link_libraries(nativerender PUBLIC ${EGL-lib} ${GLES-lib} ${libace-lib} libace_napi.z.so libc++.a)

Napi模塊注冊

  1. static napi_value Init(napi_env env, napi_value exports)
  2. {
  3. // 定義暴露在模塊上的方法
  4. napi_property_descriptor desc[] ={
  5. DECLARE_NAPI_FUNCTION("changeColor", PluginRender::NapiChangeColor),
  6. };
  7. // 通過此接口開發(fā)者可在exports上掛載native方法(即上面的PluginRender::NapiChangeColor),exports會通過js引擎綁定到js層的一個js對象
  8. NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
  9. return exports;
  10. }
  11. static napi_module nativerenderModule = {
  12. .nm_version = 1,
  13. .nm_flags = 0,
  14. .nm_filename = nullptr,
  15. .nm_register_func = Init, // 指定加載對應模塊時的回調函數(shù)
  16. .nm_modname = "nativerender", // 指定模塊名稱,對于XComponent相關開發(fā),這個名稱必須和ArkTS側XComponent中l(wèi)ibraryname的值保持一致
  17. .nm_priv = ((void*)0),
  18. .reserved = { 0 },
  19. };
  20. extern "C" __attribute__((constructor)) void RegisterModule(void)
  21. {
  22. // 注冊so模塊
  23. napi_module_register(&nativerenderModule);
  24. }

解析XComponent組件的NativeXComponent實例

NativeXComponent為XComponent提供了在native層的實例,可作為js層和native層XComponent綁定的橋梁。XComponent所提供的的NDK接口都依賴于該實例。具體NDK接口可參考Native XComponent。

可以在模塊被加載時的回調內(即Napi模塊注冊中的Init函數(shù))解析獲得NativeXComponent實例

  1. {
  2. // ...
  3. napi_status status;
  4. napi_value exportInstance = nullptr;
  5. OH_NativeXComponent *nativeXComponent = nullptr;
  6. // 用來解析出被wrap了NativeXComponent指針的屬性
  7. status = napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance);
  8. if (status != napi_ok) {
  9. return false;
  10. }
  11. // 通過napi_unwrap接口,解析出NativeXComponent的實例指針
  12. status = napi_unwrap(env, exportInstance, reinterpret_cast<void**>(&nativeXComponent));
  13. // ...
  14. }

注冊XComponent事件回調

依賴解析XComponent組件的NativeXComponent實例拿到的NativeXComponent指針,通過OH_NativeXComponent_RegisterCallback接口進行回調注冊

  1. {
  2. ...
  3. OH_NativeXComponent *nativeXComponent = nullptr;
  4. // 解析出NativeXComponent實例
  5. OH_NativeXComponent_Callback callback;
  6. callback->OnSurfaceCreated = OnSurfaceCreatedCB; // surface創(chuàng)建成功后觸發(fā),開發(fā)者可以從中獲取native window的句柄
  7. callback->OnSurfaceChanged = OnSurfaceChangedCB; // surface發(fā)生變化后觸發(fā),開發(fā)者可以從中獲取native window的句柄以及XComponent的變更信息
  8. callback->OnSurfaceDestroyed = OnSurfaceDestroyedCB; // surface銷毀時觸發(fā),開發(fā)者可以在此釋放資源
  9. callback->DispatchTouchEvent = DispatchTouchEventCB; // XComponent的touch事件回調接口,開發(fā)者可以從中獲得此次touch事件的信息
  10. OH_NativeXComponent_RegisterCallback(nativeXComponent, callback);
  11. ...
  12. }

創(chuàng)建EGL/OpenGLES環(huán)境

在注冊的OnSurfaceCreated回調中開發(fā)者能拿到native window的句柄(其本質就是XComponent所單獨擁有的NativeWindow),因此可以在這里創(chuàng)建應用自己的EGL/OpenGLES開發(fā)環(huán)境,由此開始具體渲染邏輯的開發(fā)。

  1. EGLCore* eglCore_; // EGLCore為封裝了OpenGL相關接口的類
  2. uint64_t width_;
  3. uint64_t height_;
  4. void OnSurfaceCreatedCB(OH_NativeXComponent* component, void* window)
  5. {
  6. int32_t ret = OH_NativeXComponent_GetXComponentSize(component, window, &width_, &height_);
  7. if (ret === OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
  8. eglCore_->GLContextInit(window, width_, height_); // 初始化OpenGL環(huán)境
  9. }
  10. }

ArkTS側語法介紹

開發(fā)者在ArkTS側使用如下代碼即可用XComponent組件進行利用EGL/OpenGLES渲染的開發(fā)。

  1. XComponent({ id: 'xcomponentId1', type: 'surface', libraryname: 'nativerender' })
  2. .onLoad((context) => {})
  3. .onDestroy(() => {})
  • id : 與XComponent組件為一一對應關系,不可重復。通常開發(fā)者可以在native側通過OH_NativeXComponent_GetXComponentId接口來獲取對應的id從而綁定對應的XComponent。
  • libraryname:加載模塊的名稱,必須與在native側Napi模塊注冊時nm_modname的名字一致。
    說明
    應用加載模塊實現(xiàn)跨語言調用有兩種方式:
    1. 使用NAPI的import方式加載:
      1. import nativerender from "libnativerender.so"
    2. 使用XComponent組件加載,本質也是使用了NAPI機制來加載。

      該加載方式和import加載方式的區(qū)別在于,在加載動態(tài)庫是會將XComponent的NativeXComponent實例暴露到應用的native層中,從而讓開發(fā)者可以使用XComponent的NDK接口。

  • onLoad事件
    • 觸發(fā)時刻:XComponent準備好surface后觸發(fā)。
    • 參數(shù)context:其上面掛載了暴露在模塊上的native方法,使用方法類似于利用 import context2 from "libnativerender.so" 直接加載模塊后獲得的context2實例。
    • 時序:onLoad事件的觸發(fā)和Surface相關,其和native側的OnSurfaceCreated的時序如下圖:

  • onDestroy事件

    觸發(fā)時刻:XComponent組件被銷毀時觸發(fā)與一般ArkUI的組件銷毀時機一致,其和native側的OnSurfaceDestroyed的時序如下圖:

媒體數(shù)據(jù)寫入

XComponent所持有的NativeWindow符合“生產(chǎn)者-消費者”模型

HarmonyOS上Camera、AVPlayer等符合生產(chǎn)者設計的部件都可以將數(shù)據(jù)寫入XComponent持有的NativeWindow并通過XComponent顯示。

開發(fā)者可通過綁定XComponentController獲得對應XComponent的surfaceId(該id可以唯一確定一個surface),從而傳給相應的部件接口。

  1. @State surfaceId:string = "";
  2. mXComponentController: XComponentController = new XComponentController();
  3. XComponent({ id: '', type: 'surface', controller: this.mXComponentController })
  4. .onLoad(() => {
  5. this.surfaceId = this.mXComponentController.getXComponentSurfaceId()
  6. })

具體部件接口可參考: VideoPlayer、 等。

component類型

XComponent設置為component類型時通常用于在XComponent內部執(zhí)行非UI邏輯以實現(xiàn)動態(tài)加載顯示內容的目的。

說明

type為"component"時,XComponent作為容器,子組件沿垂直方向布局:

  • 垂直方向上對齊格式:FlexAlign.Start
  • 水平方向上對齊格式:FlexAlign.Center

不支持所有的事件響應。

布局方式更改和事件響應均可通過掛載子組件來設置。

內部所寫的非UI邏輯需要封裝在一個或多個函數(shù)內。

場景示例

  1. @Builder
  2. function addText(label: string): void {
  3. Text(label)
  4. .fontSize(40)
  5. }
  6. @Entry
  7. @Component
  8. struct Index {
  9. @State message: string = 'Hello XComponent'
  10. @State messageCommon: string = 'Hello World'
  11. build() {
  12. Row() {
  13. Column() {
  14. XComponent({ id: 'xcomponentId-container', type: 'component' }) {
  15. addText(this.message)
  16. Divider()
  17. .margin(4)
  18. .strokeWidth(2)
  19. .color('#F1F3F5')
  20. .width("80%")
  21. Column() {
  22. Text(this.messageCommon)
  23. .fontSize(30)
  24. }
  25. }
  26. }
  27. .width('100%')
  28. }
  29. .height('100%')
  30. }
  31. }

相關實例

針對XComponent,有以下示例工程可供參考:

以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號