• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# CustomDraw (XComponent)
2
3
4As a drawing component, the \<[XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md)> is usually used to meet relatively complex drawing customization requirements, for example, display of a camera preview stream and drawing of a game image.
5
6
7You can specify the **type** parameter to implement different features. Two options are mainly available for this parameter: **surface** and **component**.
8
9
10With the **\<XComponent>** of the **surface** type, you can pass data to the [NativeWindow](../graphics/native-window-guidelines.md) object independently owned by it to render the image.
11
12
13With the **\<XComponent>** of the **component** type, you can dynamically load the displayed content.
14
15
16## surface Type
17
18When the **\<XComponent>** is set to the **surface** type, you can write EGL/OpenGL ES and media data and display it on the **\<XComponent>**.
19
20You can also have the **\<XComponent>** laid out and rendered together with other components.
21
22The **\<XComponent>** has an independent **NativeWindow** object, which provides a native window for you to create the EGL/OpenGL ES environment on the native (C/C++) side and use the standard OpenGL ES for development.
23
24In addition, media-related applications (such as videos and cameras) can write data to the **NativeWindow** object provided by the **\<XComponent>** to present the corresponding image.
25
26
27## Using EGL/OpenGL ES for Rendering
28
29
30### Key Points of Native Code Development
31
32Applications use native APIs to implement interactions between JS and C/C++ code. This is also the case with the **\<XComponent>**. For details, see [Using N-APIs in Application Projects](../napi/napi-guidelines.md).
33
34The type of the file for processing the JS logic on the native side is .so.
35
36- Each module has a .so file.
37
38- The .so file is named in the format of lib{moduleName}.so.
39
40
41In the scenario where the **\<XComponent>** is used for standard OpenGL ES development, the content of the **CMAKELists.txt** file is as follows:
42
43
44
45```
46cmake_minimum_required(VERSION 3.4.1)
47project(XComponent) # Project name
48
49set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
50# Path for searching for header files
51include_directories(${NATIVERENDER_ROOT_PATH}
52                    ${NATIVERENDER_ROOT_PATH}/include
53                    )
54
55# Compile the target .so file. SHARED indicates the dynamic library.
56add_library(nativerender SHARED
57            xxx.cpp
58            )
59
60# Search for related libraries (including OpenGL ES libraries and NDK APIs provided by the <XComponent>).
61find_library( EGL-lib
62              EGL )
63
64find_library( GLES-lib
65              GLESv3 )
66
67find_library( libace-lib
68              ace_ndk.z )
69
70# Dependencies required for compiling .so files
71target_link_libraries(nativerender PUBLIC ${EGL-lib} ${GLES-lib} ${libace-lib} libace_napi.z.so libc++.a)
72```
73
74
75### Registering the N-API Module
76
77
78```c++
79static napi_value Init(napi_env env, napi_value exports)
80{
81	// Define the API exposed on the module.
82    napi_property_descriptor desc[] ={
83        DECLARE_NAPI_FUNCTION("changeColor", PluginRender::NapiChangeColor),
84    };
85    // You can mount the native method (PluginRender::NapiChangeColor) to exports through this API. exports is bound to a JS object at the JS layer through the JS engine.
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, // Specify the callback for when the corresponding module is loaded.
95    .nm_modname = "nativerender", // Specify the module name. For <XComponent>-related development, the name must be the same as the value of libraryname in the <XComponent> on ArkTS.
96    .nm_priv = ((void*)0),
97    .reserved = { 0 },
98};
99
100extern "C" __attribute__((constructor)) void RegisterModule(void)
101{
102    // Register the SO module.
103    napi_module_register(&nativerenderModule);c
104}
105```
106
107
108### Parsing the NativeXComponent Instance
109
110**NativeXComponent** provides an instance at the native layer for the **\<XComponent>**, which can be used as a bridge for binding with the **\<XComponent>** at the JS layer. The NDK APIs provided by the **\<XComponent>** depend on this instance. For details about the NDK APIs, see [Native XComponent](../reference/native-apis/_o_h___native_x_component.md).
111
112
113The **NativeXComponent** instance can be obtained by parsing the callback (that is, the **Init** function in [NAPI module registration](#registering-the-n-api-module)) when the module is loaded.
114
115
116
117```c++
118{
119    // ...
120    napi_status status;
121    napi_value exportInstance = nullptr;
122    OH_NativeXComponent *nativeXComponent = nullptr;
123    // Parse the attribute of the wrapped NativeXComponent pointer.
124    status = napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance);
125    if (status != napi_ok) {
126        return false;
127    }
128    // Use the napi_unwrap API to parse the NativeXComponent instance pointer.
129    status = napi_unwrap(env, exportInstance, reinterpret_cast<void**>(&nativeXComponent));
130    // ...
131}
132```
133
134
135### Registering XComponent Callback
136
137Based on the NativeXComponent pointer obtained by [parsing the NativeXComponent instance](#parsing-the-nativexcomponent-instance), perform callback registration through the **OH_NativeXComponent_RegisterCallback** API.
138
139
140
141```c++
142{
143    ...
144    OH_NativeXComponent *nativeXComponent = nullptr;
145    // Parse the NativeXComponent instance.
146
147    OH_NativeXComponent_Callback callback;
148    callback->OnSurfaceCreated = OnSurfaceCreatedCB; // Invoked when a surface is successfully created. You can obtain the handle to the native window from this event.
149    callback->OnSurfaceChanged = OnSurfaceChangedCB; // Invoked when the surface changes. You can obtain the native window handle and XComponent change information from this event.
150    callback->OnSurfaceDestroyed = OnSurfaceDestroyedCB; // Invoked when the surface is destroyed. You can release resources in this event.
151    callback->DispatchTouchEvent = DispatchTouchEventCB; // Invoked when a touch event occurs. You can obtain the touch event information from this event.
152
153    OH_NativeXComponent_RegisterCallback(nativeXComponent, callback);
154    ...
155}
156```
157
158
159### Creating the EGL/OpenGL ES Environment
160
161In the registered **OnSurfaceCreated** callback, you can obtain the handle to the native window (which is essentially the **NativeWindow** object independently owned by the **\<XComponent>**). Therefore, you can create the EGL/OpenGL ES environment for your application to start the development of the rendering logic.
162
163
164```c++
165EGLCore* eglCore_; // EGLCore is a class that encapsulates OpenGL-related APIs.
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_); // Initialize the OpenGL environment.
173    }
174}
175```
176
177
178### ArkTS Syntax
179
180You can use the **\<XComponent>** to develop EGL/OpenGL ES rendering by using the following code on the ArkTS side:
181
182
183```ts
184XComponent({ id: 'xcomponentId1', type: 'surface', libraryname: 'nativerender' })
185  .onLoad((context) => {})
186  .onDestroy(() => {})
187```
188
189- **id**: corresponds to an **\<XComponent>** and must be unique. Generally, you can use the **OH_NativeXComponent_GetXComponentId** API on the native side to obtain the corresponding ID and bind the corresponding **\<XComponent>**.
190
191- **libraryname**: name of the loaded module, which must be the same as the value of **nm_modname** used when the Napi module is registered on the native side.
192
193  >**NOTE**
194  >
195  >An application loads modules to implement cross-language invoking in either of the following modes:
196  >
197  >- Use the **import** mode of the NAPI.
198  >
199  >   ```ts
200  >   import nativerender from "libnativerender.so"
201  >   ```
202  >
203  >- Use the **\<XComponent>**.
204  >
205  >   While this mode also uses the NAPI mechanism as the **import** mode, it enables you to use the NDK APIs of the **\<XComponent>**, by having the **NativeXComponent** instance of the **\<XComponent>** exposed to the native layer of the application when the dynamic library is loaded.
206
207- **onLoad** event
208  - Trigger time: when the surface of the **\<XComponent>** is ready.
209  - **context** parameter: where the native API exposed on the module is mounted. Its usage is similar to the usage of the **context2** instance obtained after the module is directly loaded using **import context2 from "libnativerender.so"**.
210  - Time sequence: subject to the surface. The figure below shows the timing of the **onLoad** event and the **OnSurfaceCreated** event at the native layer.
211
212     ![onLoad](figures/onLoad.png)
213
214- **onDestroy** event
215
216  Trigger time: when the **\<XComponent>** is destroyed, in the same manner as that when an ArkUI component is destroyed. The figure below shows the timing of the **onDestroy** event and the **OnSurfaceDestroyed** event at the native layer.
217
218  ![onDestroy](figures/onDestroy.png)
219
220
221### Writing Media Data
222
223The **NativeWindow** object held by the **\<XComponent>** complies with the producer-consumer model.
224
225Components that comply with the producer design, such as the Camera and AVPlayer components, can write data to the **NativeWindow** object held by the **\<XComponent>** and display the data through the **\<XComponent>**.
226
227![picture-1](figures/picture-1.png)
228
229You can bind the **\<XComponent>** to the **XComponentController** to obtain the surface ID (**surfaceId**, which uniquely identifies a surface) and send it to the corresponding component API.
230
231
232```ts
233class suf{
234  surfaceId:string = "";
235  mXComponentController: XComponentController = new XComponentController();
236  set(){
237    this.surfaceId = this.mXComponentController.getXComponentSurfaceId()
238  }
239}
240@State surfaceId:string = "";
241mXComponentController: object = new XComponentController();
242XComponent({ id: '', type: 'surface', controller: this.mXComponentController })
243  .onLoad(() => {
244    let sufset = new suf()
245    sufset.set()
246  })
247```
248
249For details about component APIs, see [AVPlayer](../reference/apis-media-kit/js-apis-media.md#avplayer9) and [Camera](../reference/apis-camera-kit/js-apis-camera.md).
250
251
252### component Type
253
254When the **\<XComponent>** is set to the **component** type, you can execute non-UI logic to dynamically load the displayed content.
255
256
257>**NOTE**
258>
259> When **type** is set to **component**, the **\<XComponent>** functions as a container, where child components are laid out vertically.
260>
261> - Vertical alignment: [FlexAlign](../reference/apis-arkui/arkui-ts/ts-appendix-enums.md#flexalign).Start
262>
263> - Horizontal alignment: [FlexAlign](../reference/apis-arkui/arkui-ts/ts-appendix-enums.md#flexalign).Center
264>
265> The component does not respond to any events.
266>
267> Layout changes and event responses can be set by mounting child components.
268>
269> The non-UI logic written internally needs to be encapsulated in one or more functions.
270
271
272### Example Scenario
273
274
275```ts
276@Builder
277function addText(label: string): void {
278  Text(label)
279    .fontSize(40)
280}
281
282@Entry
283@Component
284struct Index {
285  @State message: string = 'Hello XComponent'
286  @State messageCommon: string = 'Hello World'
287  build() {
288    Row() {
289      Column() {
290        XComponent({ id: 'xcomponentId-container', type: 'component' }) {
291          addText(this.message)
292          Divider()
293            .margin(4)
294            .strokeWidth(2)
295            .color('#F1F3F5')
296            .width("80%")
297          Column() {
298            Text(this.messageCommon)
299              .fontSize(30)
300          }
301        }
302      }
303      .width('100%')
304    }
305    .height('100%')
306  }
307}
308```
309
310![en-us_image_0000001511900428](figures/en-us_image_0000001511900428.png)
311