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  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  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 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 311