• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# XComponent
2
3
4[XComponent](../reference/arkui-ts/ts-basic-components-xcomponent.md)组件作为一种绘制组件,通常用于满足开发者较为复杂的自定义绘制需求,例如相机预览流的显示和游戏画面的绘制。
5
6
7其可通过指定其type字段来实现不同的功能,主要有两个“surface”和“component”字段可供选择。
8
9
10对于“surface”类型,开发者可将相关数据传入XComponent单独拥有的“surface”来渲染画面。
11
12
13对于“component”类型则主要用于实现动态加载显示内容的目的。
14
15
16## surface类型
17
18XComponent设置为surface类型时通常用于EGL/OpenGLES和媒体数据写入,并将其显示在XComponent组件上。
19
20设置为“surface“类型时XComponent组件可以和其他组件一起进行布局和渲染。
21
22同时XComponent又拥有单独的“surface“,可以为开发者在native侧提供native window用来创建EGL/OpenGLES环境,进而使用标准的OpenGL ES开发。
23
24除此之外,媒体相关应用(视频、相机等)也可以将相关数据写入XComponent所提供的surface,从而实现呈现相应画面。
25
26
27## 使用EGL/OpenGLES渲染
28
29
30### native侧代码开发要点
31
32OpenHarmony的应用如果要通过js来桥接native,一般需要使用napi接口来处理js交互,XComponent同样不例外,具体使用请参考[Native API在应用工程中的使用指导](../napi/napi-guidelines.md)。
33
34Native侧处理js逻辑的文件类型为so:
35
36- 每个模块对应一个so
37
38- so的命名规则为 lib{模块名}.so
39
40
41对于使用XComponent进行标准OpenGL ES开发的场景,CMAKELists.txt文件内容大致如下:
42
43
44
45```
46cmake_minimum_required(VERSION 3.4.1)
47project(XComponent) # 项目名称
48
49set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
50# 头文件查找路径
51include_directories(${NATIVERENDER_ROOT_PATH}
52                    ${NATIVERENDER_ROOT_PATH}/include
53                    )
54
55# 编译目标so,SHARED表示动态库
56add_library(nativerender SHARED
57            xxx.cpp
58            )
59
60# 查找相关库 (包括OpenGL ES相关库和XComponent提供的ndk接口)
61find_library( EGL-lib
62              EGL )
63
64find_library( GLES-lib
65              GLESv3 )
66
67find_library( libace-lib
68              ace_ndk.z )
69
70# 编译so所需要的依赖
71target_link_libraries(nativerender PUBLIC ${EGL-lib} ${GLES-lib} ${libace-lib} libace_napi.z.so libc++.a)
72```
73
74
75### Napi模块注册
76
77
78```c++
79static napi_value Init(napi_env env, napi_value exports)
80{
81	// 定义暴露在模块上的方法
82    napi_property_descriptor desc[] ={
83        DECLARE_NAPI_FUNCTION("changeColor", PluginRender::NapiChangeColor),
84    };
85    // 通过此接口开发者可在exports上挂载native方法(即上面的PluginRender::NapiChangeColor),exports会通过js引擎绑定到js层的一个js对象
86    NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
87    return exports;
88}
89
90static napi_module nativerenderModule = {
91    .nm_version = 1,
92    .nm_flags = 0,
93    .nm_filename = nullptr,
94    .nm_register_func = Init, // 指定加载对应模块时的回调函数
95    .nm_modname = "nativerender", // 指定模块名称,对于XComponent相关开发,这个名称必须和ArkTS侧XComponent中libraryname的值保持一致
96    .nm_priv = ((void*)0),
97    .reserved = { 0 },
98};
99
100extern "C" __attribute__((constructor)) void RegisterModule(void)
101{
102    // 注册so模块
103    napi_module_register(&nativerenderModule);c
104}
105```
106
107
108### 解析XComponent组件的NativeXComponent实例
109
110NativeXComponent为XComponent提供了在native层的实例,可作为js层和native层XComponent绑定的桥梁。XComponent所提供的的NDK接口都依赖于该实例。具体NDK接口可参考[Native XComponent](../reference/native-apis/_o_h___native_x_component.md)。
111
112
113可以在模块被加载时的回调内(即[Napi模块注册](#napi模块注册)中的Init函数)解析获得NativeXComponent实例
114
115
116
117```c++
118{
119    // ...
120    napi_status status;
121    napi_value exportInstance = nullptr;
122    OH_NativeXComponent *nativeXComponent = nullptr;
123    // 用来解析出被wrap了NativeXComponent指针的属性
124    status = napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance);
125    if (status != napi_ok) {
126        return false;
127    }
128    // 通过napi_unwrap接口,解析出NativeXComponent的实例指针
129    status = napi_unwrap(env, exportInstance, reinterpret_cast<void**>(&nativeXComponent));
130    // ...
131}
132```
133
134
135### 注册XComponent事件回调
136
137依赖[解析XComponent组件的NativeXComponent实例](#解析xcomponent组件的nativexcomponent实例)拿到的NativeXComponent指针,通过OH_NativeXComponent_RegisterCallback接口进行回调注册
138
139
140
141```c++
142{
143    ...
144    OH_NativeXComponent *nativeXComponent = nullptr;
145    // 解析出NativeXComponent实例
146
147    OH_NativeXComponent_Callback callback;
148    callback->OnSurfaceCreated = OnSurfaceCreatedCB; // surface创建成功后触发,开发者可以从中获取native window的句柄
149    callback->OnSurfaceChanged = OnSurfaceChangedCB; // surface发生变化后触发,开发者可以从中获取native window的句柄以及XComponent的变更信息
150    callback->OnSurfaceDestroyed = OnSurfaceDestroyedCB; // surface销毁时触发,开发者可以在此释放资源
151    callback->DispatchTouchEvent = DispatchTouchEventCB; // XComponent的touch事件回调接口,开发者可以从中获得此次touch事件的信息
152
153    OH_NativeXComponent_RegisterCallback(nativeXComponent, callback);
154    ...
155}
156```
157
158
159### 创建EGL/OpenGLES环境
160
161在注册的OnSurfaceCreated回调中开发者能拿到native window的句柄(其本质就是XComponent所单独拥有的surface),因此可以在这里创建应用自己的EGL/OpenGLES开发环境,由此开始具体渲染逻辑的开发。
162
163
164```c++
165EGLCore* eglCore_; // EGLCore为封装了OpenGL相关接口的类
166uint64_t width_;
167uint64_t height_;
168void OnSurfaceCreatedCB(OH_NativeXComponent* component, void* window)
169{
170    int32_t ret = OH_NativeXComponent_GetXComponentSize(component, window, &width_, &height_);
171    if (ret === OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
172        eglCore_->GLContextInit(window, width_, height_); // 初始化OpenGL环境
173    }
174}
175```
176
177
178### ArkTS侧语法介绍
179
180开发者在ArkTS侧使用如下代码即可用XComponent组件进行利用EGL/OpenGLES渲染的开发。
181
182
183```ts
184XComponent({ id: 'xcomponentId1', type: 'surface', libraryname: 'nativerender' })
185  .onLoad((context) => {})
186  .onDestroy(() => {})
187```
188
189- id : 与XComponent组件为一一对应关系,不可重复。通常开发者可以在native侧通过OH_NativeXComponent_GetXComponentId接口来获取对应的id从而绑定对应的XComponent。
190
191- libraryname:加载模块的名称,必须与在native侧Napi模块注册时nm_modname的名字一致。
192
193  >**说明:**
194  >
195  >   应用加载模块实现跨语言调用有两种方式:
196  >
197  > 1. 使用NAPI的import方式加载:
198  >
199  >    ```ts
200  >    import nativerender from "libnativerender.so"
201  >    ```
202  >
203  > 2. 使用XComponent组件加载,本质也是使用了NAPI机制来加载。
204  >    该加载方式和import加载方式的区别在于,在加载动态库是会将XComponent的NativeXComponent实例暴露到应用的native层中,从而让开发者可以使用XComponent的NDK接口。
205
206- onLoad事件
207  - 触发时刻:XComponent准备好surface后触发。
208  - 参数context:其上面挂载了暴露在模块上的native方法,使用方法类似于利用 import context2 from "libnativerender.so" 直接加载模块后获得的context2实例。
209  - 时序:onLoad事件的触发和Surface相关,其和native侧的OnSurfaceCreated的时序如下图:
210
211     ![图片2](figures/图片2.png)
212
213- onDestroy事件
214
215  触发时刻:XComponent组件被销毁时触发与一般ArkUI的组件销毁时机一致,其和native侧的OnSurfaceDestroyed的时序如下图:
216
217  ![图片3](figures/图片3.png)
218
219
220### 媒体数据写入
221
222XComponent所持有的Surface符合“生产者-消费者”模型
223
224OpenHarmony上Camera、VideoPlayer等符合生产者设计的部件都可以将数据写入XComponent持有的surface并通过XComponent显示。
225
226![图片1](figures/图片1.png)
227
228开发者可通过绑定XComponentController获得对应XComponent的surfaceId(该id可以唯一确定一个surface),从而传给相应的部件接口。
229
230
231```ts
232@State surfaceId:string = "";
233mXComponentController: XComponentController = new XComponentController();
234XComponent({ id: '', type: 'surface', controller: this.mXComponentController })
235  .onLoad(() => {
236    this.surfaceId = this.mXComponentController.getXComponentSurfaceId()
237  })
238```
239
240具体部件接口可参考:[AVPlayer](../reference/apis/js-apis-media.md#avplayer9)、[Camera](../reference/apis/js-apis-camera.md) 等。
241
242
243### component类型
244
245XComponent设置为component类型时通常用于在XComponent内部执行非UI逻辑以实现动态加载显示内容的目的。
246
247
248>**说明:**
249>
250> type为"component"时,XComponent作为容器,子组件沿垂直方向布局:
251>
252> - 垂直方向上对齐格式:[FlexAlign](../reference/arkui-ts/ts-appendix-enums.md#flexalign).Start
253>
254> - 水平方向上对齐格式:[FlexAlign](../reference/arkui-ts/ts-appendix-enums.md#flexalign).Center
255>
256> 不支持所有的事件响应。
257>
258> 布局方式更改和事件响应均可通过挂载子组件来设置。
259>
260> 内部所写的非UI逻辑需要封装在一个或多个函数内。
261
262
263### 场景示例
264
265
266```ts
267@Builder
268function addText(label: string): void {
269  Text(label)
270    .fontSize(40)
271}
272
273@Entry
274@Component
275struct Index {
276  @State message: string = 'Hello XComponent'
277  @State messageCommon: string = 'Hello World'
278  build() {
279    Row() {
280      Column() {
281        XComponent({ id: 'xcomponentId-container', type: 'component' }) {
282          addText(this.message)
283          Divider()
284            .margin(4)
285            .strokeWidth(2)
286            .color('#F1F3F5')
287            .width("80%")
288          Column() {
289            Text(this.messageCommon)
290              .fontSize(30)
291          }
292        }
293      }
294      .width('100%')
295    }
296    .height('100%')
297  }
298}
299```
300
301![zh-cn_image_0000001511900428](figures/zh-cn_image_0000001511900428.png)
302