diff --git a/OAT.xml b/OAT.xml
index 8c9842e9ed6a58789c5f21add8b50fbbae20560e..896fc38a47595b8c31b0406959ca1d9aa7cfddf6 100644
--- a/OAT.xml
+++ b/OAT.xml
@@ -189,6 +189,12 @@ Note:If the text contains special characters, please escape them according to th
+
+
+
+
+
+
diff --git a/code/BasicFeature/Native/NativeXComponent/.gitignore b/code/BasicFeature/Native/NativeXComponent/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d2ff20141ceed86d87c0ea5d99481973005bab2b
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/.gitignore
@@ -0,0 +1,12 @@
+/node_modules
+/oh_modules
+/local.properties
+/.idea
+**/build
+/.hvigor
+.cxx
+/.clangd
+/.clang-format
+/.clang-tidy
+**/.test
+/.appanalyzer
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/AppScope/app.json5 b/code/BasicFeature/Native/NativeXComponent/AppScope/app.json5
new file mode 100644
index 0000000000000000000000000000000000000000..33e40e31af85a79aaa9242b628f577e0157a7ee4
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/AppScope/app.json5
@@ -0,0 +1,10 @@
+{
+ "app": {
+ "bundleName": "com.example.nativexcomponent",
+ "vendor": "example",
+ "versionCode": 1000000,
+ "versionName": "1.0.0",
+ "icon": "$media:app_icon",
+ "label": "$string:app_name"
+ }
+}
diff --git a/code/BasicFeature/Native/NativeXComponent/AppScope/resources/base/element/string.json b/code/BasicFeature/Native/NativeXComponent/AppScope/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..79421a57da43fb2a471e3ade57c18b8d845b3647
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/AppScope/resources/base/element/string.json
@@ -0,0 +1,8 @@
+{
+ "string": [
+ {
+ "name": "app_name",
+ "value": "NativeXComponent"
+ }
+ ]
+}
diff --git a/code/BasicFeature/Native/NativeXComponent/AppScope/resources/base/media/app_icon.png b/code/BasicFeature/Native/NativeXComponent/AppScope/resources/base/media/app_icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..a39445dc87828b76fed6d2ec470dd455c45319e3
Binary files /dev/null and b/code/BasicFeature/Native/NativeXComponent/AppScope/resources/base/media/app_icon.png differ
diff --git a/code/BasicFeature/Native/NativeXComponent/README_zh.md b/code/BasicFeature/Native/NativeXComponent/README_zh.md
new file mode 100644
index 0000000000000000000000000000000000000000..521314df5fa9518c035b68f9c044ef21d5883e08
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/README_zh.md
@@ -0,0 +1,111 @@
+# XComponent
+
+### 介绍
+
+本示例主要介绍开发者如何使用ArkUI NDK API创建XComponent组件进行自定义绘制。主要包括:创建组件,获取ArkUI_NodeHandle和OH_ArkUI_SurfaceHolder实例,注册Surface生命周期和无障碍回调,设置可变帧率,获取NativeWindow后使用OpenGL ES/EGL接口在XComponent组件上进行图形绘制。功能主要包括绘制一个五角星,期望帧率设置,点击按钮开启/关闭软键盘,并可以通过点击XComponent区域改变五角星的颜色。
+
+### 效果预览
+
+| 绘制五角星 | 改变颜色 |
+|-----------------------------------------------|-----------------------------------------------------|
+|  |  |
+
+使用说明
+
+1. 安装编译生成的hap包,并打开应用。
+3. 点击XComponent组件区域(页面中蓝色五角星所在区域)改变五角星颜色。
+3. 点击“单指点击XComponent软键盘消失”对应区域,软键盘拉起;点击XComponent组件区域,软键盘消失。
+4. 点击needSoftKeyboard=false 按钮,点击“单指点击XComponent软键盘消失”对应区域,软键盘拉起;点击XComponent组件区域,软键盘不消失。
+5. 拖动期望帧率设置下的滑动条,可以控制期望帧率,具体通过打印日志的速度来衡量,日志检索"OnFrameCallback count"关键字。
+
+
+### 工程目录
+
+```
+├──entry/src/main
+│ ├──cpp // C++代码区
+│ │ ├──CMakeLists.txt // CMake配置文件
+│ │ ├──napi_init.cpp // Napi模块注册
+│ │ ├──manager // 生命周期管理模块
+│ │ │ ├──plugin_manager.cpp
+│ │ │ └──plugin_manager.h
+│ │ ├──render // 渲染模块
+│ │ │ ├──EGLConst.h
+│ │ │ ├──EGLRender.cpp
+│ │ │ └──EGLRender.h
+| | ├──types
+| | | ├──libnativerender
+| | | | ├──Index.d.ts
+| | | | ├──oh-package.json5
+│ ├──ets // ets代码区
+│ │ ├──entryability
+│ │ │ ├──EntryAbility.ts // 程序入口类
+| | ├──entrybackupability
+| | | ├──EntryBackupAbility.ets
+│ │ ├──pages // 页面文件
+│ │ ├──Index.ets // 主界面
+| ├──resources // 资源文件目录
+```
+
+### 具体实现
+
+通过在IDE中创建Native c++ 工程,在c++代码中定义对外接口为BindNode,在ArkTS侧调用该接口用于注册Surface生命周期、事件和无障碍等回调,在回调逻辑中实现绘制五角星的逻辑。
+
+基于OH_ArkUI_SurfaceHolder实例,在Native侧调用OH_ArkUI_XComponent_GetNativeWindow创建NativeWindow实例并初始化
+EGL环境。在Native侧的OnsurfaceChanged回调中,传入OH_ArkUI_SurfaceHolder、宽和高,并以此为输入调用EGL相关的接口改变对应NativeWindow的尺寸和内容。
+
+源码参考:[main目录](entry/src/main/)下的文件。涉及到的相关接口:
+
+| 接口名 | 描述 |
+| ------------------------------------------------------------ | ------------------------------------------------------------ |
+| OH_ArkUI_QueryModuleInterfaceByName(ArkUI_NativeAPIVariantKind type, const char* structName) | 获取指定类型的Native模块接口集合。 |
+| OH_ArkUI_XComponent_GetNativeWindow(OH_ArkUI_SurfaceHolder* surfaceHolder) | 获取与OH_ArkUI_SurfaceHolder实例关联的nativeWindow。 |
+| OH_ArkUI_SurfaceHolder_RemoveSurfaceCallback(OH_ArkUI_SurfaceHolder* surfaceHolder, OH_ArkUI_SurfaceCallback* callback) | 从OH_ArkUI_SurfaceHolder实例中移除先前添加的Surface生命周期回调。 |
+| OH_ArkUI_SurfaceCallback_Dispose(OH_ArkUI_SurfaceCallback* callback) | 释放OH_ArkUI_SurfaceCallback对象。 |
+| OH_ArkUI_SurfaceHolder_Dispose(OH_ArkUI_SurfaceHolder* surfaceHolder) | 释放OH_ArkUI_SurfaceHolder对象。 |
+| OH_ArkUI_NodeEvent_GetEventType(ArkUI_NodeEvent* event) | 从组件事件获取事件类型。 |
+| OH_ArkUI_NodeEvent_GetNodeHandle(ArkUI_NodeEvent* event) | 获取触发组件事件的组件对象。 |
+| OH_ArkUI_GetNodeHandleFromNapiValue(napi_env env, napi_value frameNode, ArkUI_NodeHandle* handle) | 获取ArkTS侧创建的FrameNode节点对象映射到native侧的ArkUI_NodeHandle。 |
+| OH_ArkUI_SurfaceHolder_Create(ArkUI_NodeHandle node) | 从XComponent节点创建一个OH_ArkUI_SurfaceHolder对象。 |
+| OH_ArkUI_SurfaceCallback_Create() | 创建一个OH_ArkUI_SurfaceCallback对象。 |
+| OH_ArkUI_SurfaceCallback_SetSurfaceCreatedEvent(OH_ArkUI_SurfaceCallback* callback, void (\*onSurfaceCreated)(OH_ArkUI_SurfaceHolder* surfaceHolder)) | 往OH_ArkUI_SurfaceCallback对象中注册onSurfaceCreated回调。 |
+| OH_ArkUI_SurfaceCallback_SetSurfaceChangedEvent(OH_ArkUI_SurfaceCallback* callback, void (\*onSurfaceChanged)(OH_ArkUI_SurfaceHolder* surfaceHolder, uint64_t width, uint64_t height)) | 往OH_ArkUI_SurfaceCallback对象中注册onSurfaceChanged回调。 |
+| OH_ArkUI_SurfaceCallback_SetSurfaceDestroyedEvent(OH_ArkUI_SurfaceCallback* callback, void (\*onSurfaceDestroyed)(OH_ArkUI_SurfaceHolder* surfaceHolder)) | 往OH_ArkUI_SurfaceCallback对象中注册onSurfaceDestroyed回调。 |
+| OH_ArkUI_SurfaceCallback_SetSurfaceShowEvent(OH_ArkUI_SurfaceCallback* callback, void (\*onSurfaceShow)(OH_ArkUI_SurfaceHolder* surfaceHolder)) | 往OH_ArkUI_SurfaceCallback对象中注册onSurfaceShow回调。 |
+| OH_ArkUI_SurfaceCallback_SetSurfaceHideEvent(OH_ArkUI_SurfaceCallback* callback, void (\*onSurfaceHide)(OH_ArkUI_SurfaceHolder* surfaceHolder)) | 往OH_ArkUI_SurfaceCallback对象中注册onSurfaceHide回调。 |
+| OH_ArkUI_XComponent_RegisterOnFrameCallback(ArkUI_NodeHandle node, void (*callback)(ArkUI_NodeHandle node, uint64_t timestamp, uint64_t targetTimestamp)) | 为XComponent节点注册onFrame回调。 |
+| OH_ArkUI_SurfaceHolder_AddSurfaceCallback(OH_ArkUI_SurfaceHolder* surfaceHolder, OH_ArkUI_SurfaceCallback* callback) | 往OH_ArkUI_SurfaceHolder实例注册OH_ArkUI_SurfaceCallback对象。 |
+| OH_ArkUI_AccessibilityProvider_Create(ArkUI_NodeHandle node) | 从XComponent节点创建一个ArkUI_AccessibilityProvider对象。 |
+| OH_ArkUI_XComponent_UnregisterOnFrameCallback(ArkUI_NodeHandle node) | 取消注册XComponent节点的onFrame回调。 |
+| OH_ArkUI_AccessibilityProvider_Dispose(ArkUI_AccessibilityProvider* provider) | 释放ArkUI_AccessibilityProvider对象。 |
+| OH_ArkUI_XComponent_SetExpectedFrameRateRange(ArkUI_NodeHandle node, OH_NativeXComponent_ExpectedRateRange range) | 为XComponent节点设置预期的帧率范围。 |
+| OH_ArkUI_XComponent_SetNeedSoftKeyboard(ArkUI_NodeHandle node, bool needSoftKeyboard) | 设置XComponent节点在获得焦点时是否需要显示软键盘。 |
+
+
+### 相关权限
+
+不涉及。
+
+### 依赖
+
+不涉及。
+
+### 约束与限制
+
+1. 本示例仅支持标准系统上运行,支持设备:rk3568
+
+2. 本示例为Stage模型,支持API20版本SDK,SDK版本号(API Version 20 Release),镜像版本号(6.0 Release)
+
+3. 本示例需要使用DevEco Studio 版本号(6.0.0 Release)及以上版本才可编译运行
+
+### 下载
+
+如需单独下载本工程,执行如下命令:
+
+```
+git init
+git config core.sparsecheckout true
+echo code/BasicFeature/Native/NativeXComponent/ > .git/info/sparse-checkout
+git remote add origin https://gitee.com/openharmony/applications_app_samples.git
+git pull origin master
+```
diff --git a/code/BasicFeature/Native/NativeXComponent/build-profile.json5 b/code/BasicFeature/Native/NativeXComponent/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..4d722c4fd8dbfcbfde8df7af59d9aa69c9fbbc58
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/build-profile.json5
@@ -0,0 +1,49 @@
+{
+ "app": {
+ "signingConfigs": [
+ {
+ "name": "default",
+ "material": {
+ "certpath": "C:/Users/ygb/.ohos/config/openharmony/default_NativeXComponent_vettAfdtjXzJupm970gSS6vIj2Id7Ze2ciVtUQXR8Rg=.cer",
+ "storePassword": "0000001BEC892530464D6962B4D77B1E79E860C5875717DE664123F7D2E55BEC70B43186A43D9DD1956329",
+ "keyAlias": "debugKey",
+ "keyPassword": "0000001BE98DB5A9C0AF95C89DF9A44792F28CBCC21C552EABE1D78459C56AC35BB46E34BEED2E591E1202",
+ "profile": "C:/Users/ygb/.ohos/config/openharmony/default_NativeXComponent_vettAfdtjXzJupm970gSS6vIj2Id7Ze2ciVtUQXR8Rg=.p7b",
+ "signAlg": "SHA256withECDSA",
+ "storeFile": "C:/Users/ygb/.ohos/config/openharmony/default_NativeXComponent_vettAfdtjXzJupm970gSS6vIj2Id7Ze2ciVtUQXR8Rg=.p12"
+ }
+ }
+ ],
+ "products": [
+ {
+ "name": "default",
+ "signingConfig": "default",
+ "compileSdkVersion": 12,
+ "compatibleSdkVersion": 12,
+ "runtimeOS": "OpenHarmony",
+ }
+ ],
+ "buildModeSet": [
+ {
+ "name": "debug",
+ },
+ {
+ "name": "release"
+ }
+ ]
+ },
+ "modules": [
+ {
+ "name": "entry",
+ "srcPath": "./entry",
+ "targets": [
+ {
+ "name": "default",
+ "applyToProducts": [
+ "default"
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/.gitignore b/code/BasicFeature/Native/NativeXComponent/entry/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/.gitignore
@@ -0,0 +1,6 @@
+/node_modules
+/oh_modules
+/.preview
+/build
+/.cxx
+/.test
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/build-profile.json5 b/code/BasicFeature/Native/NativeXComponent/entry/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..b57619b89ac0041c92a141816fea427abb42bacd
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/build-profile.json5
@@ -0,0 +1,40 @@
+{
+ "apiType": "stageMode",
+ "buildOption": {
+ "externalNativeOptions": {
+ "path": "./src/main/cpp/CMakeLists.txt",
+ "arguments": "",
+ "cppFlags": "",
+ "abiFilters": ["arm64-v8a", "armeabi-v7a", "x86_64"]
+ }
+ },
+ "buildOptionSet": [
+ {
+ "name": "release",
+ "arkOptions": {
+ "obfuscation": {
+ "ruleOptions": {
+ "enable": true,
+ "files": [
+ "./obfuscation-rules.txt"
+ ]
+ }
+ }
+ },
+ "nativeLib": {
+ "debugSymbol": {
+ "strip": true,
+ "exclude": []
+ }
+ }
+ },
+ ],
+ "targets": [
+ {
+ "name": "default"
+ },
+ {
+ "name": "ohosTest",
+ }
+ ]
+}
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/hvigorfile.ts b/code/BasicFeature/Native/NativeXComponent/entry/hvigorfile.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c6edcd90486dd5a853cf7d34c8647f08414ca7a3
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/hvigorfile.ts
@@ -0,0 +1,6 @@
+import { hapTasks } from '@ohos/hvigor-ohos-plugin';
+
+export default {
+ system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
+ plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
+}
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/oh-package.json5 b/code/BasicFeature/Native/NativeXComponent/entry/oh-package.json5
new file mode 100644
index 0000000000000000000000000000000000000000..811a0b10e04f84946f75b69e8b72f3bf7ec35000
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/oh-package.json5
@@ -0,0 +1,11 @@
+{
+ "name": "entry",
+ "version": "1.0.0",
+ "description": "Please describe the basic information.",
+ "main": "",
+ "author": "",
+ "license": "",
+ "dependencies": {
+ "libnativerender.so": "file:./src/main/cpp/types/libnativerender"
+ }
+}
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/CMakeLists.txt b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..382141fc2d6da550b6a5108b36455c3e910f03a4
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/CMakeLists.txt
@@ -0,0 +1,67 @@
+# the minimum version of CMake.
+cmake_minimum_required(VERSION 3.5.0)
+project(LCNXComponent2)
+
+set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
+
+if(DEFINED PACKAGE_FIND_FILE)
+ include(${PACKAGE_FIND_FILE})
+endif()
+
+include_directories(${NATIVERENDER_ROOT_PATH}
+ ${NATIVERENDER_ROOT_PATH}/render
+ ${NATIVERENDER_ROOT_PATH}/manager)
+
+add_library(nativerender SHARED
+ render/EGLRender.cpp
+ manager/plugin_manager.cpp
+ napi_init.cpp)
+find_library(
+ # Sets the name of the path variable.
+ EGL-lib
+ # Specifies the name of the NDK library that
+ # you want CMake to locate.
+ EGL
+)
+
+find_library(
+ # Sets the name of the path variable.
+ GLES-lib
+ # Specifies the name of the NDK library that
+ # you want CMake to locate.
+ GLESv3
+)
+
+find_library(
+ # Sets the name of the path variable.
+ hilog-lib
+ # Specifies the name of the NDK library that
+ # you want CMake to locate.
+ hilog_ndk.z
+)
+
+find_library(
+ # Sets the name of the path variable.
+ libace-lib
+ # Specifies the name of the NDK library that
+ # you want CMake to locate.
+ ace_ndk.z
+)
+
+find_library(
+ # Sets the name of the path variable.
+ libnapi-lib
+ # Specifies the name of the NDK library that
+ # you want CMake to locate.
+ ace_napi.z
+)
+
+find_library(
+ # Sets the name of the path variable.
+ libuv-lib
+ # Specifies the name of the NDK library that
+ # you want CMake to locate.
+ uv
+)
+
+target_link_libraries(nativerender PUBLIC ${EGL-lib} ${GLES-lib} ${hilog-lib} ${libace-lib} ${libnapi-lib} ${libuv-lib} libnative_window.so)
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/manager/plugin_manager.cpp b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/manager/plugin_manager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ed521a4eb735340c953dc7e94d8001b5ee867e4f
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/manager/plugin_manager.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "plugin_manager.h"
+#include
+#include
+#include
+#include
+
+namespace NativeXComponentSample {
+std::unordered_map PluginManager::nodeHandleMap_;
+std::unordered_map PluginManager::renderMap_;
+std::unordered_map PluginManager::callbackMap_;
+std::unordered_map PluginManager::surfaceHolderMap_;
+ArkUI_AccessibilityProvider *PluginManager::provider_ = nullptr;
+ArkUI_NativeNodeAPI_1 *nodeAPI = reinterpret_cast(
+ OH_ArkUI_QueryModuleInterfaceByName(ARKUI_NATIVE_NODE, "ArkUI_NativeNodeAPI_1"));
+namespace {
+std::string value2String(napi_env env, napi_value value) // 将napi_value转化为string类型的变量
+{
+ size_t stringSize = 0;
+ napi_get_value_string_utf8(env, value, nullptr, 0, &stringSize);
+ std::string valueString;
+ valueString.resize(stringSize);
+ napi_get_value_string_utf8(env, value, &valueString[0], stringSize + 1, &stringSize);
+ return valueString;
+}
+
+void OnSurfaceCreated(OH_ArkUI_SurfaceHolder *holder)
+{
+ auto window = OH_ArkUI_XComponent_GetNativeWindow(holder); // 获取native window
+ auto render = new EGLRender();
+ PluginManager::renderMap_[holder] = render;
+ render->SetUpEGLContext(window);
+}
+
+void OnSurfaceChanged(OH_ArkUI_SurfaceHolder *holder, uint64_t width, uint64_t height)
+{
+ if (PluginManager::renderMap_.count(holder)) {
+ auto render = PluginManager::renderMap_[holder];
+ render->SetEGLWindowSize(width, height); // 设置绘制区域大小
+ render->DrawStar(true); // 绘制五角星
+ }
+}
+
+void OnSurfaceDestroyed(OH_ArkUI_SurfaceHolder *holder)
+{
+ OH_LOG_Print(LOG_APP, LOG_ERROR, 0xff00, "onBind", "on destroyed");
+ if (PluginManager::renderMap_.count(holder)) { // 销毁render对象
+ auto render = PluginManager::renderMap_[holder];
+ delete render;
+ PluginManager::renderMap_.erase(holder);
+ }
+ if (PluginManager::callbackMap_.count(holder)) {
+ auto callback = PluginManager::callbackMap_[holder];
+ OH_ArkUI_SurfaceHolder_RemoveSurfaceCallback(holder, callback); // 移除SurfaceCallback
+ OH_ArkUI_SurfaceCallback_Dispose(callback); // 销毁surfaceCallback
+ PluginManager::callbackMap_.erase(holder);
+ }
+ OH_ArkUI_SurfaceHolder_Dispose(holder); // 销毁surfaceHolder
+}
+
+void OnSurfaceShow(OH_ArkUI_SurfaceHolder *holder)
+{
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "on surface show");
+}
+
+void OnSurfaceHide(OH_ArkUI_SurfaceHolder *holder)
+{
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "on surface hide");
+}
+
+void OnFrameCallback(ArkUI_NodeHandle node, uint64_t timestamp, uint64_t targetTimestamp)
+{
+ if (!PluginManager::surfaceHolderMap_.count(node)) {
+ return;
+ }
+ static uint64_t count = 0;
+ count++;
+ if (count % FRAME_COUNT == 0) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "OnFrameCallback count = %{public}ld", count);
+ }
+}
+
+void onEvent(ArkUI_NodeEvent *event)
+{
+ auto eventType = OH_ArkUI_NodeEvent_GetEventType(event); // 获取组件事件类型
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "on event");
+ if (eventType == NODE_TOUCH_EVENT) {
+ ArkUI_NodeHandle handle = OH_ArkUI_NodeEvent_GetNodeHandle(event); // 获取触发该事件的组件对象
+ auto holder = PluginManager::surfaceHolderMap_[handle];
+ if (PluginManager::renderMap_.count(holder)) {
+ auto render = PluginManager::renderMap_[holder];
+ render->DrawStar(false); // 绘制五角星
+ }
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "on touch");
+ }
+}
+} // namespace
+
+napi_value PluginManager::BindNode(napi_env env, napi_callback_info info)
+{
+ size_t argc = 2;
+ napi_value args[2] = {nullptr};
+ napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
+ std::string nodeId = value2String(env, args[0]);
+ ArkUI_NodeHandle handle;
+ OH_ArkUI_GetNodeHandleFromNapiValue(env, args[1], &handle); // 获取 nodeHandle
+ OH_ArkUI_SurfaceHolder *holder = OH_ArkUI_SurfaceHolder_Create(handle); // 获取 SurfaceHolder
+ nodeHandleMap_[nodeId] = handle;
+ surfaceHolderMap_[handle] = holder;
+ auto callback = OH_ArkUI_SurfaceCallback_Create(); // 创建 SurfaceCallback
+ callbackMap_[holder] = callback;
+ OH_ArkUI_SurfaceCallback_SetSurfaceCreatedEvent(callback, OnSurfaceCreated); // 注册OnSurfaceCreated回调
+ OH_ArkUI_SurfaceCallback_SetSurfaceChangedEvent(callback, OnSurfaceChanged); // 注册OnSurfaceChanged回调
+ OH_ArkUI_SurfaceCallback_SetSurfaceDestroyedEvent(callback, OnSurfaceDestroyed); // 注册OnSurfaceDestroyed回调
+ OH_ArkUI_SurfaceCallback_SetSurfaceShowEvent(callback, OnSurfaceShow); // 注册OnSurfaceShow回调
+ OH_ArkUI_SurfaceCallback_SetSurfaceHideEvent(callback, OnSurfaceHide); // 注册OnSurfaceHide回调
+ OH_ArkUI_XComponent_RegisterOnFrameCallback(handle, OnFrameCallback); // 注册OnFrameCallback回调
+ OH_ArkUI_SurfaceHolder_AddSurfaceCallback(holder, callback); // 注册SurfaceCallback回调
+ if (!nodeAPI->addNodeEventReceiver(handle, onEvent)) { // 添加事件监听,返回成功码 0
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "addNodeEventReceiver error");
+ }
+ if (!nodeAPI->registerNodeEvent(handle, NODE_TOUCH_EVENT, 0, nullptr)) { // 用C接口注册touch事件,返回成功码 0
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "registerTouchEvent error");
+ }
+ provider_ = OH_ArkUI_AccessibilityProvider_Create(handle); // 创建一个ArkUI_AccessibilityProvider类型的对象
+ /**
+ * 获取ArkUI_AccessibilityProvider后,如果注册无障碍回调函数请参考:
+ * https://gitee.com/openharmony/docs/blob/OpenHarmony-5.1.0-Release/zh-cn/application-dev/ui/ndk-accessibility-xcomponent.md
+ * **/
+ return nullptr;
+}
+
+napi_value PluginManager::UnbindNode(napi_env env, napi_callback_info info)
+{
+ size_t argc = 1;
+ napi_value args[1] = {nullptr};
+ napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
+ std::string nodeId = value2String(env, args[0]);
+ auto node = nodeHandleMap_[nodeId];
+ OH_ArkUI_XComponent_UnregisterOnFrameCallback(node); // 解注册帧回调
+ OH_ArkUI_AccessibilityProvider_Dispose(provider_); // 销毁 ArkUI_AccessibilityProvider
+ nodeAPI->disposeNode(node); // 销毁nodeHandle
+ nodeHandleMap_.erase(nodeId);
+ return nullptr;
+}
+
+napi_value PluginManager::SetFrameRate(napi_env env, napi_callback_info info)
+{
+ size_t argc = 4;
+ napi_value args[4] = {nullptr};
+ napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
+ std::string nodeId = value2String(env, args[0]);
+ auto node = nodeHandleMap_[nodeId];
+
+ int32_t min = 0;
+ napi_get_value_int32(env, args[FIRST_ARG], &min);
+
+ int32_t max = 0;
+ napi_get_value_int32(env, args[SECOND_ARG], &max);
+
+ int32_t expected = 0;
+ napi_get_value_int32(env, args[THIRD_ARG], &expected);
+ OH_NativeXComponent_ExpectedRateRange range = {.min = min, .max = max, .expected = expected};
+ OH_ArkUI_XComponent_SetExpectedFrameRateRange(node, range); // 设置期望帧率
+ return nullptr;
+}
+
+napi_value PluginManager::SetNeedSoftKeyboard(napi_env env, napi_callback_info info)
+{
+ size_t argc = 2;
+ napi_value args[2] = {nullptr};
+ napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
+ std::string nodeId = value2String(env, args[0]);
+ auto node = nodeHandleMap_[nodeId];
+
+ bool needSoftKeyboard = false;
+ napi_get_value_bool(env, args[1], &needSoftKeyboard);
+ OH_ArkUI_XComponent_SetNeedSoftKeyboard(node, needSoftKeyboard); // 设置是否需要软键盘
+ return nullptr;
+}
+} // namespace NativeXComponentSample
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/manager/plugin_manager.h b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/manager/plugin_manager.h
new file mode 100644
index 0000000000000000000000000000000000000000..ac59f7b645c786f2cf556a892197f9a8845292a0
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/manager/plugin_manager.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NATIVEXCOMPONENT_PLUGIN_MANAGER_H
+#define NATIVEXCOMPONENT_PLUGIN_MANAGER_H
+
+#include "render/EGLRender.h"
+#include
+#include "napi/native_api.h"
+#include
+#include
+#include
+
+namespace NativeXComponentSample {
+constexpr const int FIRST_ARG = 1;
+constexpr const int SECOND_ARG = 2;
+constexpr const int THIRD_ARG = 3;
+constexpr const int FRAME_COUNT = 50;
+class PluginManager {
+public:
+ ~PluginManager();
+ static napi_value BindNode(napi_env env, napi_callback_info info);
+ static napi_value UnbindNode(napi_env env, napi_callback_info info);
+ static napi_value SetFrameRate(napi_env env, napi_callback_info info);
+ static napi_value SetNeedSoftKeyboard(napi_env env, napi_callback_info info);
+
+public:
+ static std::unordered_map nodeHandleMap_;
+ static std::unordered_map renderMap_;
+ static std::unordered_map callbackMap_;
+ static std::unordered_map surfaceHolderMap_;
+ static ArkUI_AccessibilityProvider *provider_;
+};
+} // namespace NativeXComponentSample
+#endif // NATIVEXCOMPONENT_PLUGIN_MANAGER_H
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/napi_init.cpp b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/napi_init.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..290c5c093d0564c05f8ab76ab94e2822aa1b0c82
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/napi_init.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "manager/plugin_manager.h"
+
+namespace NativeXComponentSample {
+
+// 在napi_init.cpp文件中,Init方法注册接口函数,从而将封装的C++方法传递出来,供ArkTS侧调用
+EXTERN_C_START
+static napi_value Init(napi_env env, napi_value exports)
+{
+ // 向ArkTS侧暴露接口
+ napi_property_descriptor desc[] = {
+ {"bindNode", nullptr, PluginManager::BindNode, nullptr, nullptr, nullptr, napi_default, nullptr},
+ {"unbindNode", nullptr, PluginManager::UnbindNode, nullptr, nullptr, nullptr, napi_default, nullptr},
+ {"setFrameRate", nullptr, PluginManager::SetFrameRate, nullptr, nullptr, nullptr, napi_default, nullptr},
+ {"setNeedSoftKeyboard", nullptr, PluginManager::SetNeedSoftKeyboard, nullptr, nullptr, nullptr, napi_default,
+ nullptr},
+ };
+ napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
+ return exports;
+}
+EXTERN_C_END
+
+// 编写接口的描述信息,根据实际需要可以修改对应参数
+static napi_module demoModule = {
+ .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},
+};
+} // namespace NativeXComponentSample
+
+// __attribute__((constructor))修饰的方法由系统自动调用,使用Node-API接口napi_module_register()传入模块描述信息进行模块注册
+extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
+{
+ napi_module_register(&NativeXComponentSample::demoModule);
+}
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/render/EGLConst.h b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/render/EGLConst.h
new file mode 100644
index 0000000000000000000000000000000000000000..60c0ad2c3a3c8e6b98b2ac0e016f7037609f8614
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/render/EGLConst.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NATIVEXCOMPONENT_EGLCONST_H
+#define NATIVEXCOMPONENT_EGLCONST_H
+#include
+#include
+#include
+
+const unsigned int LOG_PRINT_DOMAIN = 0xFF00;
+
+/**
+ * Program error.
+ */
+const GLuint PROGRAM_ERROR = 0;
+
+/**
+ * Position error.
+ */
+const GLint POSITION_ERROR = -1;
+
+/**
+ * Default x position.
+ */
+const int DEFAULT_X_POSITION = 0;
+
+/**
+ * Default y position.
+ */
+const int DEFAULT_Y_POSITION = 0;
+
+/**
+ * Gl red default.
+ */
+const GLfloat GL_RED_DEFAULT = 0.0;
+
+/**
+ * Gl green default.
+ */
+const GLfloat GL_GREEN_DEFAULT = 0.0;
+
+/**
+ * Gl blue default.
+ */
+const GLfloat GL_BLUE_DEFAULT = 0.0;
+
+/**
+ * Gl alpha default.
+ */
+const GLfloat GL_ALPHA_DEFAULT = 1.0;
+
+/**
+ * Pointer size.
+ */
+const GLint POINTER_SIZE = 2;
+
+/**
+ * Triangle fan size.
+ */
+const GLsizei TRIANGLE_FAN_SIZE = 4;
+
+/**
+ * Fifty percent.
+ */
+const float FIFTY_PERCENT = 0.5;
+
+/**
+ * Position handle name.
+ */
+const char POSITION_NAME[] = "a_position";
+
+/**
+ * Background color #f4f4f4.
+ */
+const GLfloat BACKGROUND_COLOR[] = {244.0f / 255, 244.0f / 255, 244.0f / 255, 1.0f};
+
+/**
+ * Draw color #7E8FFB.
+ */
+const GLfloat DRAW_COLOR[] = {126.0f / 255, 143.0f / 255, 251.0f / 255, 1.0f};
+
+/**
+ * Change color #92D6CC.
+ */
+const GLfloat CHANGE_COLOR[] = {146.0f / 255, 214.0f / 255, 204.0f / 255, 1.0f};
+
+/**
+ * Background area.
+ */
+const GLfloat BACKGROUND_RECTANGLE_VERTICES[] = {-1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f};
+
+const EGLint ATTRIB_LIST[] = {
+ // Key,value.
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ // End.
+ EGL_NONE};
+
+const EGLint CONTEXT_ATTRIBS[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
+
+/**
+ * Vertex shader.
+ */
+const char VERTEX_SHADER[] = "#version 300 es\n"
+ "layout(location = 0) in vec4 a_position;\n"
+ "layout(location = 1) in vec4 a_color; \n"
+ "out vec4 v_color; \n"
+ "void main() \n"
+ "{ \n"
+ " gl_Position = a_position; \n"
+ " v_color = a_color; \n"
+ "} \n";
+
+/**
+ * Fragment shader.
+ */
+const char FRAGMENT_SHADER[] = "#version 300 es\n"
+ "precision mediump float; \n"
+ "in vec4 v_color; \n"
+ "out vec4 fragColor; \n"
+ "void main() \n"
+ "{ \n"
+ " fragColor = v_color; \n"
+ "} \n";
+#endif // NATIVEXCOMPONENT_EGLCONST_H
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/render/EGLRender.cpp b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/render/EGLRender.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..46b80cedd77341ae7cf18adee6e7959f401f2ed9
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/render/EGLRender.cpp
@@ -0,0 +1,302 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "EGLRender.h"
+#include "EGLConst.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace {
+void Rotate2d(GLfloat centerX, GLfloat centerY, GLfloat *rotateX, GLfloat *rotateY, GLfloat theta)
+{
+ GLfloat tempX = cos(theta) * (*rotateX - centerX) - sin(theta) * (*rotateY - centerY);
+ GLfloat tempY = sin(theta) * (*rotateX - centerX) + cos(theta) * (*rotateY - centerY);
+ *rotateX = tempX + centerX;
+ *rotateY = tempY + centerY;
+}
+
+GLuint LoadShader(GLenum type, const char *shaderSrc)
+{
+ if ((type <= 0) || (shaderSrc == nullptr)) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "glCreateShader type or shaderSrc error");
+ return PROGRAM_ERROR;
+ }
+
+ GLuint shader = glCreateShader(type);
+ if (shader == 0) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "glCreateShader unable to load shader");
+ return PROGRAM_ERROR;
+ }
+
+ // The gl function has no return value.
+ glShaderSource(shader, 1, &shaderSrc, nullptr);
+ glCompileShader(shader);
+
+ GLint compiled;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+ if (compiled != 0) {
+ return shader;
+ }
+
+ GLint infoLen = 0;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
+ if (infoLen <= 1) {
+ glDeleteShader(shader);
+ return PROGRAM_ERROR;
+ }
+
+ char *infoLog = (char *)malloc(sizeof(char) * (infoLen + 1));
+ if (infoLog != nullptr) {
+ memset(infoLog, 0, infoLen + 1);
+ glGetShaderInfoLog(shader, infoLen, nullptr, infoLog);
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "glCompileShader error = %s", infoLog);
+ free(infoLog);
+ infoLog = nullptr;
+ }
+ glDeleteShader(shader);
+ return PROGRAM_ERROR;
+}
+
+GLuint CreateProgram(const char *vertexShader, const char *fragShader)
+{
+ if ((vertexShader == nullptr) || (fragShader == nullptr)) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender",
+ "createProgram: vertexShader or fragShader is null");
+ return PROGRAM_ERROR;
+ }
+
+ GLuint vertex = LoadShader(GL_VERTEX_SHADER, vertexShader);
+ if (vertex == PROGRAM_ERROR) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "createProgram vertex error");
+ return PROGRAM_ERROR;
+ }
+
+ GLuint fragment = LoadShader(GL_FRAGMENT_SHADER, fragShader);
+ if (fragment == PROGRAM_ERROR) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "createProgram fragment error");
+ return PROGRAM_ERROR;
+ }
+
+ GLuint program = glCreateProgram();
+ if (program == PROGRAM_ERROR) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "createProgram program error");
+ glDeleteShader(vertex);
+ glDeleteShader(fragment);
+ return PROGRAM_ERROR;
+ }
+
+ // The gl function has no return value.
+ glAttachShader(program, vertex);
+ glAttachShader(program, fragment);
+ glLinkProgram(program);
+
+ GLint linked;
+ glGetProgramiv(program, GL_LINK_STATUS, &linked);
+ if (linked != 0) {
+ glDeleteShader(vertex);
+ glDeleteShader(fragment);
+ return program;
+ }
+
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "createProgram linked error");
+ GLint infoLen = 0;
+ glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen);
+ if (infoLen > 1) {
+ char *infoLog = (char *)malloc(sizeof(char) * (infoLen + 1));
+ memset(infoLog, 0, infoLen + 1);
+ glGetProgramInfoLog(program, infoLen, nullptr, infoLog);
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "glLinkProgram error = %s", infoLog);
+ free(infoLog);
+ infoLog = nullptr;
+ }
+ glDeleteShader(vertex);
+ glDeleteShader(fragment);
+ glDeleteProgram(program);
+ return PROGRAM_ERROR;
+}
+} // namespace
+
+bool EGLRender::SetUpEGLContext(void *window)
+{
+ OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "EGLRender", "EglContextInit execute");
+ eglWindow_ = (EGLNativeWindowType)(window);
+ // Init display.
+ eglDisplay_ = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if (eglDisplay_ == EGL_NO_DISPLAY) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "eglGetDisplay: unable to get EGL display");
+ return false;
+ }
+ EGLint majorVersion;
+ EGLint minorVersion;
+ if (!eglInitialize(eglDisplay_, &majorVersion, &minorVersion)) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender",
+ "eglInitialize: unable to get initialize EGL display");
+ return false;
+ };
+ // Select configuration.
+ const EGLint maxConfigSize = 1;
+ EGLint numConfigs;
+ if (!eglChooseConfig(eglDisplay_, ATTRIB_LIST, &eglConfig_, maxConfigSize, &numConfigs)) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "eglChooseConfig: unable to choose configs");
+ return false;
+ };
+ // CreateEnvironment.
+ // Create surface.
+ eglSurface_ = eglCreateWindowSurface(eglDisplay_, eglConfig_, eglWindow_, NULL);
+ if (eglSurface_ == nullptr) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender",
+ "eglCreateWindowSurface: unable to create surface");
+ return false;
+ }
+ if (eglSurface_ == nullptr) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender",
+ "eglCreateWindowSurface: unable to create surface");
+ return false;
+ }
+ // Create context.
+ eglContext_ = eglCreateContext(eglDisplay_, eglConfig_, EGL_NO_CONTEXT, CONTEXT_ATTRIBS);
+ if (!eglMakeCurrent(eglDisplay_, eglSurface_, eglSurface_, eglContext_)) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "eglMakeCurrent failed");
+ return false;
+ }
+ // Create program.
+ program_ = CreateProgram(VERTEX_SHADER, FRAGMENT_SHADER);
+ if (program_ == PROGRAM_ERROR) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "CreateProgram: unable to create program");
+ return false;
+ }
+ return true;
+}
+
+GLint EGLRender::PrepareDraw()
+{
+ if ((eglDisplay_ == nullptr) || (eglSurface_ == nullptr) || (eglContext_ == nullptr) ||
+ (!eglMakeCurrent(eglDisplay_, eglSurface_, eglSurface_, eglContext_))) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "PrepareDraw: param error");
+ return POSITION_ERROR;
+ }
+
+ // The gl function has no return value.
+ glViewport(DEFAULT_X_POSITION, DEFAULT_Y_POSITION, width_, height_);
+ glClearColor(GL_RED_DEFAULT, GL_GREEN_DEFAULT, GL_BLUE_DEFAULT, GL_ALPHA_DEFAULT);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glUseProgram(program_);
+
+ return glGetAttribLocation(program_, POSITION_NAME);
+}
+
+void EGLRender::DrawStar(bool drawColor)
+{
+ OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "EGLRender", "Draw");
+ GLint position = PrepareDraw();
+ if (position == POSITION_ERROR) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "Draw get position failed");
+ return;
+ }
+
+ if (!ExecuteDraw(position, BACKGROUND_COLOR, BACKGROUND_RECTANGLE_VERTICES)) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "Draw execute draw background failed");
+ return;
+ }
+
+ // Divided into five quadrilaterals and calculate one of the quadrilateral's Vertices
+ GLfloat rotateX = 0;
+ GLfloat rotateY = FIFTY_PERCENT * height_;
+ GLfloat centerX = 0;
+ // Convert DEG(54° & 18°) to RAD
+ GLfloat centerY = -rotateY * (M_PI / 180 * 54) * (M_PI / 180 * 18);
+ // Convert DEG(18°) to RAD
+ GLfloat leftX = -rotateY * (M_PI / 180 * 18);
+ GLfloat leftY = 0;
+ // Convert DEG(18°) to RAD
+ GLfloat rightX = rotateY * (M_PI / 180 * 18);
+ GLfloat rightY = 0;
+
+ const GLfloat shapeVertices[] = {centerX / width_, centerY / height_, leftX / width_, leftY / height_,
+ rotateX / width_, rotateY / height_, rightX / width_, rightY / height_};
+ auto color = drawColor ? DRAW_COLOR : CHANGE_COLOR;
+ if (!ExecuteDraw(position, color, shapeVertices)) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "Draw execute draw shape failed");
+ return;
+ }
+
+ // Convert DEG(72°) to RAD
+ GLfloat rad = M_PI / 180 * 72;
+ // Rotate four times
+ for (int i = 0; i < 4; ++i) {
+ Rotate2d(centerX, centerY, &rotateX, &rotateY, rad);
+ Rotate2d(centerX, centerY, &leftX, &leftY, rad);
+ Rotate2d(centerX, centerY, &rightX, &rightY, rad);
+
+ const GLfloat shapeVertices[] = {centerX / width_, centerY / height_, leftX / width_, leftY / height_,
+ rotateX / width_, rotateY / height_, rightX / width_, rightY / height_};
+
+ if (!ExecuteDraw(position, color, shapeVertices)) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "Draw execute draw shape failed");
+ return;
+ }
+ }
+ // 将绘制命令提交给GPU,GPU执行完成后将渲染结果显示到屏幕
+ glFlush();
+ glFinish();
+ if (!eglSwapBuffers(eglDisplay_, eglSurface_)) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "Draw FinishDraw failed");
+ return;
+ }
+}
+
+bool EGLRender::ExecuteDraw(GLint position, const GLfloat *color, const GLfloat shapeVertices[])
+{
+ if ((position > 0) || (color == nullptr)) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "ExecuteDraw: param error");
+ return false;
+ }
+
+ // The gl function has no return value.
+ glVertexAttribPointer(position, POINTER_SIZE, GL_FLOAT, GL_FALSE, 0, shapeVertices);
+ glEnableVertexAttribArray(position);
+ glVertexAttrib4fv(1, color);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, TRIANGLE_FAN_SIZE);
+ glDisableVertexAttribArray(position);
+
+ return true;
+}
+
+void EGLRender::SetEGLWindowSize(int width, int height)
+{
+ width_ = width;
+ height_ = height;
+}
+
+EGLRender::~EGLRender()
+{
+ if ((eglDisplay_ == nullptr) || (eglSurface_ == nullptr) || (!eglDestroySurface(eglDisplay_, eglSurface_))) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, 0xff00, "EGLRender", "Release eglDestroySurface failed");
+ }
+
+ if ((eglDisplay_ == nullptr) || (eglContext_ == nullptr) || (!eglDestroyContext(eglDisplay_, eglContext_))) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, 0xff00, "EGLRender", "Release eglDestroySurface failed");
+ }
+
+ if ((eglDisplay_ == nullptr) || (!eglTerminate(eglDisplay_))) {
+ OH_LOG_Print(LOG_APP, LOG_ERROR, 0xff00, "EGLRender", "Release eglDestroySurface failed");
+ }
+}
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/render/EGLRender.h b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/render/EGLRender.h
new file mode 100644
index 0000000000000000000000000000000000000000..7eb3bb812b2ed37687898e5bf9c66e2de44e1733
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/render/EGLRender.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NATIVEXCOMPONENT_EGLRENDER_H
+#define NATIVEXCOMPONENT_EGLRENDER_H
+
+#include "EGLConst.h"
+#include
+#include
+#include
+#include
+#include
+
+class EGLRender {
+public:
+ bool SetUpEGLContext(void *window);
+ void SetEGLWindowSize(int width, int height);
+ void DrawStar(bool drawColor);
+
+ std::string xcomponentId;
+ EGLNativeWindowType eglWindow_;
+
+ EGLDisplay eglDisplay_ = EGL_NO_DISPLAY;
+ EGLConfig eglConfig_ = EGL_NO_CONFIG_KHR;
+ EGLSurface eglSurface_ = EGL_NO_SURFACE;
+ EGLContext eglContext_ = EGL_NO_CONTEXT;
+ GLuint program_;
+ int width_ = 0;
+ int height_ = 0;
+ ~EGLRender();
+
+private:
+ GLint PrepareDraw();
+ bool ExecuteDraw(GLint position, const GLfloat *color, const GLfloat shapeVertices[]);
+};
+#endif // NATIVEXCOMPONENT_EGLRENDER_H
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/types/libnativerender/Index.d.ts b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/types/libnativerender/Index.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ede5fd44a7b0569ed2e24d1e0b6bd39a11ebd509
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/types/libnativerender/Index.d.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export const bindNode: (id: string, node: object) => void;
+export const unbindNode: (id: string) => void;
+export const setFrameRate: (id: string, min: number, max: number, expected: number) => void;
+export const setNeedSoftKeyboard: (id: string, needSoftKeyborad: boolean) => void;
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/types/libnativerender/oh-package.json5 b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/types/libnativerender/oh-package.json5
new file mode 100644
index 0000000000000000000000000000000000000000..78545bfb4030791657a728b8cc790495fd7e29be
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/cpp/types/libnativerender/oh-package.json5
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+{
+ "name": "libnativerender.so",
+ "types": "./Index.d.ts",
+ "version": "1.0.0",
+ "description": "Please describe the basic information."
+}
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/ets/entryability/EntryAbility.ets b/code/BasicFeature/Native/NativeXComponent/entry/src/main/ets/entryability/EntryAbility.ets
new file mode 100644
index 0000000000000000000000000000000000000000..6a1b23532e867a0d7c63e1b6c1b9c21b0350ae88
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/ets/entryability/EntryAbility.ets
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { window } from '@kit.ArkUI';
+
+export default class EntryAbility extends UIAbility {
+ onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
+ }
+
+ onDestroy(): void {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
+ }
+
+ onWindowStageCreate(windowStage: window.WindowStage): void {
+ // Main window is created, set main page for this ability
+ hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
+
+ windowStage.loadContent('pages/Index', (err) => {
+ if (err.code) {
+ hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
+ return;
+ }
+ hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
+ });
+ }
+
+ onWindowStageDestroy(): void {
+ // Main window is destroyed, release UI related resources
+ hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
+ }
+
+ onForeground(): void {
+ // Ability has brought to foreground
+ hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
+ }
+
+ onBackground(): void {
+ // Ability has back to background
+ hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
+ }
+};
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/code/BasicFeature/Native/NativeXComponent/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets
new file mode 100644
index 0000000000000000000000000000000000000000..2a89be4fd2886b169f4a62f1a1f3cbf7b9211c08
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit';
+
+export default class EntryBackupAbility extends BackupExtensionAbility {
+ async onBackup() {
+ hilog.info(0x0000, 'testTag', 'onBackup ok');
+ }
+
+ async onRestore(bundleVersion: BundleVersion) {
+ hilog.info(0x0000, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion));
+ }
+}
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/ets/pages/Index.ets b/code/BasicFeature/Native/NativeXComponent/entry/src/main/ets/pages/Index.ets
new file mode 100644
index 0000000000000000000000000000000000000000..b2fe86027d78fa2e4b17aa0c54fac5c48106b129
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/ets/pages/Index.ets
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import native from 'libnativerender.so';
+
+@Entry
+@Component
+struct Index {
+ xcomponentId: string = 'xcp' + (new Date().getTime());
+ @State isShow: boolean = true;
+ @State minRate: number = 0;
+ @State maxRate: number = 120;
+ @State expected: number = 60;
+ needSoftKeyboard: boolean = false;
+ @State needSoftKeyboardState: string = 'needSoftKeyboard=' + this.needSoftKeyboard;
+ @State text: string = '单指点击XComponent软键盘消失'
+ controller: TextInputController = new TextInputController()
+
+ build() {
+ Column() {
+ TextInput({ text: this.text, placeholder: 'please input ...', controller: this.controller })
+ .id('textInput')
+ .placeholderColor(Color.Grey)
+ .placeholderFont({ size: 14, weight: 400 })
+ .caretColor(Color.Blue)
+ .width(400)
+ .height(40)
+ .margin(20)
+ .fontSize(14)
+ .fontColor(Color.Black)
+ .onChange((value: string) => {
+ this.text = value
+ })
+ Column() {
+ if (this.isShow) {
+ XComponent({
+ type: XComponentType.SURFACE
+ })
+ .id(this.xcomponentId)
+ .onAttach(() => {
+ let node = this.getUIContext().getFrameNodeById(this.xcomponentId)
+ native.bindNode(this.xcomponentId, node)
+ })
+ .onDetach(() => {
+ native.unbindNode(this.xcomponentId)
+ })
+ .width(200)
+ .height(200)
+ .focusable(true)
+ .focusOnTouch(true)
+ .defaultFocus(true)
+ }
+ }.height(200)
+
+ Button('创建/销毁').onClick(() => {
+ this.isShow = !this.isShow;
+ })
+
+ Column() {
+ Text('期望帧率设置:')
+ .textAlign(TextAlign.Start)
+ .fontSize(15)
+ .border({ width: 1 })
+ .padding(10)
+ .width('100%')
+ .margin(5)
+ Text('min: ' + this.minRate)
+ Slider({
+ value: this.minRate,
+ min: 0,
+ max: 240,
+ step: 1
+ }).onChange((value: number, mode: SliderChangeMode) => {
+ this.minRate = value;
+ native.setFrameRate(this.xcomponentId, this.minRate, this.maxRate, this.expected)
+ }).width('100%')
+ .id('minSlider')
+ Text('max: ' + this.maxRate)
+ Slider({
+ value: this.maxRate,
+ min: 0,
+ max: 240,
+ step: 1
+ }).onChange((value: number, mode: SliderChangeMode) => {
+ this.maxRate = value;
+ native.setFrameRate(this.xcomponentId, this.minRate, this.maxRate, this.expected)
+ }).width('100%')
+ .id('maxSlider')
+ Text('expected: ' + this.expected)
+ Slider({
+ value: this.expected,
+ min: 0,
+ max: 240,
+ step: 1
+ }).onChange((value: number, mode: SliderChangeMode) => {
+ this.expected = value;
+ native.setFrameRate(this.xcomponentId, this.minRate, this.maxRate, this.expected)
+ }).width('100%')
+ .id('expectedSlider')
+ }.backgroundColor("#F0FAFF")
+
+ Button(this.needSoftKeyboardState)
+ .onClick(() => {
+ this.needSoftKeyboard = !this.needSoftKeyboard;
+ this.needSoftKeyboardState = 'needSoftKeyboard=' + this.needSoftKeyboard;
+ native.setNeedSoftKeyboard(this.xcomponentId, this.needSoftKeyboard);
+ this.text = this.needSoftKeyboard ? '单指点击XComponent软键盘不消失' : '单指点击XComponent软键盘消失'
+ })
+ }
+ .width('100%')
+ }
+}
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/module.json5 b/code/BasicFeature/Native/NativeXComponent/entry/src/main/module.json5
new file mode 100644
index 0000000000000000000000000000000000000000..2cd3a6c601073f86f4789ab9db7529f84f705f3f
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/module.json5
@@ -0,0 +1,51 @@
+{
+ "module": {
+ "name": "entry",
+ "type": "entry",
+ "description": "$string:module_desc",
+ "mainElement": "EntryAbility",
+ "deviceTypes": [
+ "default",
+ "tablet"
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false,
+ "pages": "$profile:main_pages",
+ "abilities": [
+ {
+ "name": "EntryAbility",
+ "srcEntry": "./ets/entryability/EntryAbility.ets",
+ "description": "$string:EntryAbility_desc",
+ "icon": "$media:layered_image",
+ "label": "$string:EntryAbility_label",
+ "startWindowIcon": "$media:startIcon",
+ "startWindowBackground": "$color:start_window_background",
+ "exported": true,
+ "skills": [
+ {
+ "entities": [
+ "entity.system.home"
+ ],
+ "actions": [
+ "action.system.home"
+ ]
+ }
+ ]
+ }
+ ],
+ "extensionAbilities": [
+ {
+ "name": "EntryBackupAbility",
+ "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
+ "type": "backup",
+ "exported": false,
+ "metadata": [
+ {
+ "name": "ohos.extension.backup",
+ "resource": "$profile:backup_config"
+ }
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/element/color.json b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/element/color.json
new file mode 100644
index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/element/color.json
@@ -0,0 +1,8 @@
+{
+ "color": [
+ {
+ "name": "start_window_background",
+ "value": "#FFFFFF"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/element/string.json b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..f94595515a99e0c828807e243494f57f09251930
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/element/string.json
@@ -0,0 +1,16 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "module description"
+ },
+ {
+ "name": "EntryAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "EntryAbility_label",
+ "value": "label"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/background.png b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/background.png
new file mode 100644
index 0000000000000000000000000000000000000000..f939c9fa8cc8914832e602198745f592a0dfa34d
Binary files /dev/null and b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/background.png differ
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/foreground.png b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/foreground.png
new file mode 100644
index 0000000000000000000000000000000000000000..4483ddad1f079e1089d685bd204ee1cfe1d01902
Binary files /dev/null and b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/foreground.png differ
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/layered_image.json b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/layered_image.json
new file mode 100644
index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/layered_image.json
@@ -0,0 +1,7 @@
+{
+ "layered-image":
+ {
+ "background" : "$media:background",
+ "foreground" : "$media:foreground"
+ }
+}
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/startIcon.png b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/startIcon.png
new file mode 100644
index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b
Binary files /dev/null and b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/media/startIcon.png differ
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/profile/backup_config.json b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/profile/backup_config.json
new file mode 100644
index 0000000000000000000000000000000000000000..78f40ae7c494d71e2482278f359ec790ca73471a
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/profile/backup_config.json
@@ -0,0 +1,3 @@
+{
+ "allowToBackupRestore": true
+}
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/profile/main_pages.json b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/profile/main_pages.json
new file mode 100644
index 0000000000000000000000000000000000000000..1898d94f58d6128ab712be2c68acc7c98e9ab9ce
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/base/profile/main_pages.json
@@ -0,0 +1,5 @@
+{
+ "src": [
+ "pages/Index"
+ ]
+}
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/en_US/element/string.json b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/en_US/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..f94595515a99e0c828807e243494f57f09251930
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/en_US/element/string.json
@@ -0,0 +1,16 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "module description"
+ },
+ {
+ "name": "EntryAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "EntryAbility_label",
+ "value": "label"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/zh_CN/element/string.json b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/zh_CN/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..597ecf95e61d7e30367c22fe2f8638008361b044
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/main/resources/zh_CN/element/string.json
@@ -0,0 +1,16 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "模块描述"
+ },
+ {
+ "name": "EntryAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "EntryAbility_label",
+ "value": "label"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/mock/Libentry.mock.ets b/code/BasicFeature/Native/NativeXComponent/entry/src/mock/Libentry.mock.ets
new file mode 100644
index 0000000000000000000000000000000000000000..c2171716d040a605ef6af71e90b937a945f2677d
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/mock/Libentry.mock.ets
@@ -0,0 +1,7 @@
+const NativeMock: Record = {
+ 'add': (a: number, b: number) => {
+ return a + b;
+ },
+};
+
+export default NativeMock;
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/mock/mock-config.json5 b/code/BasicFeature/Native/NativeXComponent/entry/src/mock/mock-config.json5
new file mode 100644
index 0000000000000000000000000000000000000000..6540976c9acc8afbd45895db6404334cff195465
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/mock/mock-config.json5
@@ -0,0 +1,5 @@
+{
+ "libentry.so": {
+ "source": "src/mock/Libentry.mock.ets"
+ }
+}
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/ohosTest/ets/test/Ability.test.ets b/code/BasicFeature/Native/NativeXComponent/entry/src/ohosTest/ets/test/Ability.test.ets
new file mode 100644
index 0000000000000000000000000000000000000000..d0a983484dc47ee92663bc333b29e68a933fcb87
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/ohosTest/ets/test/Ability.test.ets
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
+import { describe, it, expect } from '@ohos/hypium';
+import { Driver, MatchPattern, ON, MouseButton } from '@ohos.UiTest';
+
+const TAG = '[Sample_NDK_XComponent]';
+
+export default function abilityTest() {
+
+ describe('ActsAbilityTest', () => {
+ /**
+ * 打开应用
+ */
+ it('StartAbility_001', 0, async (done: Function) => {
+ console.info(TAG, 'StartAbility_001 begin');
+ let driver = Driver.create();
+ let abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
+ try {
+ await abilityDelegator.startAbility({
+ bundleName: 'com.example.nativexcomponent',
+ abilityName: 'EntryAbility'
+ });
+ } catch (exception) {
+ console.info(TAG, `StartAbility_001 exception = ${JSON.stringify(exception)}`);
+ expect().assertFail();
+ }
+ await driver.delayMs(1000);
+ // 判断XComponent onLoad回调执行成功
+ await driver.assertComponentExist(ON.text('期望帧率设置:'));
+ await driver.assertComponentExist(ON.text('单指点击XComponent软键盘消失'));
+ await driver.assertComponentExist(ON.text('创建/销毁'));
+ await driver.assertComponentExist(ON.text('needSoftKeyboard=',MatchPattern.STARTS_WITH));
+ done();
+ console.info(TAG, 'StartAbility_001 end');
+ })
+
+ /**
+ * 点击按钮,按钮
+ */
+ it('DrawShape_001', 2, async (done: Function) => {
+ console.info(TAG, 'CreateFiles_001 begin');
+ let driver = Driver.create();
+ // 判断XComponent onLoad回调执行成功
+ await driver.assertComponentExist(ON.text('单指点击XComponent软键盘消失'));
+ await driver.assertComponentExist(ON.id('xcp',MatchPattern.STARTS_WITH));
+ // 判断是否有按键
+ await driver.assertComponentExist(ON.text('创建/销毁'));
+ let drawStarBtn = await driver.findComponent(ON.text('创建/销毁'));
+ // 点击'创建/销毁'按钮
+ await drawStarBtn.click();
+ await driver.delayMs(1000);
+
+ // 判断xcomponent组件已经消失
+ let xcomponent = await driver.findComponent(ON.id('xcp', MatchPattern.STARTS_WITH));
+ expect(xcomponent === null).assertTrue();
+
+ await drawStarBtn.click();
+ await driver.delayMs(1000);
+ // 判断xcomponent组件复现
+ let xcomponent2 = await driver.findComponent(ON.id('xcp', MatchPattern.STARTS_WITH));
+ expect(xcomponent2 === null).assertFalse();
+ done();
+ console.info(TAG, 'DrawShape_001 end');
+ })
+
+ it('DrawShape_002', 2, async (done: Function) => {
+ console.info(TAG, 'DrawShape_002 begin');
+ let driver = Driver.create();
+ // 判断XComponent onLoad回调执行成功
+ await driver.assertComponentExist(ON.text('单指点击XComponent软键盘消失'));
+ await driver.assertComponentExist(ON.id('xcp',MatchPattern.STARTS_WITH));
+ // 判断是否有按键
+ await driver.assertComponentExist(ON.text('创建/销毁'));
+
+ // 点击XComponent组件
+ let xcomponent = await driver.findComponent(ON.id('xcp', MatchPattern.STARTS_WITH));
+ expect(xcomponent === null).assertFalse();
+ await xcomponent.click();
+ await driver.delayMs(1000);
+ // 判断touch回调已执行
+ await driver.assertComponentExist(ON.id('xcp',MatchPattern.STARTS_WITH));
+ done();
+ console.info(TAG, 'DrawShape_002 end');
+ })
+
+ it('SetExpectedFrameRate_001 ', 2, async (done: Function) => {
+ console.info(TAG, 'SetExpectedFrameRate_001 start');
+ let driver = await Driver.create();
+ await driver.delayMs(1000);
+ await driver.assertComponentExist(ON.text('期望帧率设置:'));
+ await driver.assertComponentExist(ON.text('单指点击XComponent软键盘消失'));
+ await driver.assertComponentExist(ON.text('创建/销毁'));
+ await driver.assertComponentExist(ON.text('needSoftKeyboard', MatchPattern.STARTS_WITH));
+
+ let minBtn = await driver.findComponent(ON.text('min', MatchPattern.STARTS_WITH));
+ expect(minBtn === null).assertFalse();
+
+ let minSlider = await driver.findComponent(ON.id('minSlider'));
+ expect(minSlider === null).assertFalse();
+ let point = await minSlider.getBoundsCenter();
+ await driver.mouseClick(point, MouseButton.MOUSE_BUTTON_LEFT);
+ await driver.delayMs(1000);
+
+ let text = await minBtn.getText();
+ expect(text.toString() == 'min: 120').assertTrue();
+
+ done();
+ console.info(TAG, 'SetExpectedFrameRate_001 end');
+ })
+
+ it('SetExpectedFrameRate_002 ', 2, async (done: Function) => {
+ console.info(TAG, 'SetExpectedFrameRate_002 start');
+ let driver = await Driver.create();
+ await driver.delayMs(1000);
+ await driver.assertComponentExist(ON.text('期望帧率设置:'));
+ await driver.assertComponentExist(ON.text('单指点击XComponent软键盘消失'));
+ await driver.assertComponentExist(ON.text('创建/销毁'));
+ await driver.assertComponentExist(ON.text('needSoftKeyboard', MatchPattern.STARTS_WITH));
+
+ let maxBtn = await driver.findComponent(ON.text('max', MatchPattern.STARTS_WITH));
+ expect(maxBtn === null).assertFalse();
+
+ let maxSlider = await driver.findComponent(ON.id('maxSlider'));
+ expect(maxSlider === null).assertFalse();
+ let rect = await maxSlider.getBounds();
+ await driver.mouseClick({x:rect.right, y:(rect.top+rect.bottom)/2}, MouseButton.MOUSE_BUTTON_LEFT);
+ await driver.delayMs(1000);
+
+ let text = await maxBtn.getText();
+ console.info('yys', text);
+ expect(text.toString() == 'max: 240').assertTrue();
+
+ done();
+ console.info(TAG, 'SetExpectedFrameRate_002 end');
+ })
+
+ it('SetExpectedFrameRate_003 ', 2, async (done: Function) => {
+ console.info(TAG, 'SetExpectedFrameRate_003 start');
+ let driver = await Driver.create();
+ await driver.delayMs(1000);
+ await driver.assertComponentExist(ON.text('期望帧率设置:'));
+ await driver.assertComponentExist(ON.text('单指点击XComponent软键盘消失'));
+ await driver.assertComponentExist(ON.text('创建/销毁'));
+ await driver.assertComponentExist(ON.text('needSoftKeyboard', MatchPattern.STARTS_WITH));
+
+ let expectedBtn = await driver.findComponent(ON.text('expected', MatchPattern.STARTS_WITH));
+ expect(expectedBtn === null).assertFalse();
+
+ let expectedSlider = await driver.findComponent(ON.id('expectedSlider'));
+ expect(expectedSlider === null).assertFalse();
+ let point = await expectedSlider.getBoundsCenter();
+ await driver.mouseClick(point, MouseButton.MOUSE_BUTTON_LEFT);
+ await driver.delayMs(1000);
+
+ let text = await expectedBtn.getText();
+ expect(text.toString() == 'expected: 120').assertTrue();
+
+ done();
+ console.info(TAG, 'SetExpectedFrameRate_003 end');
+ })
+
+ it('SetSoftKeyboard_001', 2, async (done: Function) => {
+ console.info(TAG, 'SetSoftKeyboard_001 start');
+ let driver = Driver.create();
+ await driver.delayMs(1000);
+ await driver.assertComponentExist(ON.text('期望帧率设置:'));
+ await driver.assertComponentExist(ON.text('单指点击XComponent软键盘消失'));
+ await driver.assertComponentExist(ON.text('创建/销毁'));
+ await driver.assertComponentExist(ON.text('needSoftKeyboard', MatchPattern.STARTS_WITH));
+
+ let text = await driver.findComponent(ON.id('textInput'));
+ expect(text === null).assertFalse();
+ await text.click();
+ await driver.delayMs(1000);
+ expect(ON.text('needSoftKeyboard', MatchPattern.STARTS_WITH) == null).assertFalse();
+
+ // 点击XComponent组件
+ let xcomponent = await driver.findComponent(ON.id('xcp', MatchPattern.STARTS_WITH));
+ expect(xcomponent === null).assertFalse();
+ await xcomponent.click();
+ await driver.delayMs(1000);
+
+ done();
+ console.info(TAG, 'SetSoftKeyboard_001 end');
+ })
+
+ it('SetSoftKeyboard_002', 2, async (done: Function) => {
+ console.info(TAG, 'SetSoftKeyboard_002 start');
+ let driver = Driver.create();
+ await driver.delayMs(1000);
+ await driver.assertComponentExist(ON.text('期望帧率设置:'));
+ await driver.assertComponentExist(ON.text('单指点击XComponent软键盘消失'));
+ await driver.assertComponentExist(ON.text('创建/销毁'));
+ await driver.assertComponentExist(ON.text('needSoftKeyboard', MatchPattern.STARTS_WITH));
+
+ let button_softKeyboard = await driver.findComponent(ON.text('needSoftKeyboard', MatchPattern.STARTS_WITH));
+ expect(button_softKeyboard === null).assertFalse();
+ await button_softKeyboard.click();
+ let text = await driver.findComponent(ON.id('textInput'));
+ expect(text === null).assertFalse();
+ await text.click();
+ await driver.delayMs(1000);
+ expect(ON.text('needSoftKeyboard', MatchPattern.STARTS_WITH) == null).assertFalse();
+
+ // 点击XComponent组件
+ let xcomponent = await driver.findComponent(ON.id('xcp', MatchPattern.STARTS_WITH));
+ expect(xcomponent === null).assertFalse();
+ await xcomponent.click();
+ await driver.delayMs(1000);
+
+ done();
+ console.info(TAG, 'SetSoftKeyboard_002 end');
+ })
+ })
+}
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/ohosTest/ets/test/List.test.ets b/code/BasicFeature/Native/NativeXComponent/entry/src/ohosTest/ets/test/List.test.ets
new file mode 100644
index 0000000000000000000000000000000000000000..794c7dc4ed66bd98fa3865e07922906e2fcef545
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/ohosTest/ets/test/List.test.ets
@@ -0,0 +1,5 @@
+import abilityTest from './Ability.test';
+
+export default function testsuite() {
+ abilityTest();
+}
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/ohosTest/module.json5 b/code/BasicFeature/Native/NativeXComponent/entry/src/ohosTest/module.json5
new file mode 100644
index 0000000000000000000000000000000000000000..69026872775eebd0844900b225c411959ae5608b
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/ohosTest/module.json5
@@ -0,0 +1,12 @@
+{
+ "module": {
+ "name": "entry_test",
+ "type": "feature",
+ "deviceTypes": [
+ "default",
+ "tablet"
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false
+ }
+}
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/test/List.test.ets b/code/BasicFeature/Native/NativeXComponent/entry/src/test/List.test.ets
new file mode 100644
index 0000000000000000000000000000000000000000..bb5b5c3731e283dd507c847560ee59bde477bbc7
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/test/List.test.ets
@@ -0,0 +1,5 @@
+import localUnitTest from './LocalUnit.test';
+
+export default function testsuite() {
+ localUnitTest();
+}
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/entry/src/test/LocalUnit.test.ets b/code/BasicFeature/Native/NativeXComponent/entry/src/test/LocalUnit.test.ets
new file mode 100644
index 0000000000000000000000000000000000000000..165fc1615ee8618b4cb6a622f144a9a707eee99f
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/entry/src/test/LocalUnit.test.ets
@@ -0,0 +1,33 @@
+import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
+
+export default function localUnitTest() {
+ describe('localUnitTest', () => {
+ // Defines a test suite. Two parameters are supported: test suite name and test suite function.
+ beforeAll(() => {
+ // Presets an action, which is performed only once before all test cases of the test suite start.
+ // This API supports only one parameter: preset action function.
+ });
+ beforeEach(() => {
+ // Presets an action, which is performed before each unit test case starts.
+ // The number of execution times is the same as the number of test cases defined by **it**.
+ // This API supports only one parameter: preset action function.
+ });
+ afterEach(() => {
+ // Presets a clear action, which is performed after each unit test case ends.
+ // The number of execution times is the same as the number of test cases defined by **it**.
+ // This API supports only one parameter: clear action function.
+ });
+ afterAll(() => {
+ // Presets a clear action, which is performed after all test cases of the test suite end.
+ // This API supports only one parameter: clear action function.
+ });
+ it('assertContain', 0, () => {
+ // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
+ let a = 'abc';
+ let b = 'b';
+ // Defines a variety of assertion methods, which are used to declare expected boolean conditions.
+ expect(a).assertContain(b);
+ expect(a).assertEqual(a);
+ });
+ });
+}
\ No newline at end of file
diff --git a/code/BasicFeature/Native/NativeXComponent/hvigor/hvigor-config.json5 b/code/BasicFeature/Native/NativeXComponent/hvigor/hvigor-config.json5
new file mode 100644
index 0000000000000000000000000000000000000000..06b2783670a348f95533b352c1ceda909a842bbc
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/hvigor/hvigor-config.json5
@@ -0,0 +1,22 @@
+{
+ "modelVersion": "5.0.0",
+ "dependencies": {
+ },
+ "execution": {
+ // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */
+ // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */
+ // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */
+ // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */
+ // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */
+ },
+ "logging": {
+ // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */
+ },
+ "debugging": {
+ // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */
+ },
+ "nodeOptions": {
+ // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/
+ // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/
+ }
+}
diff --git a/code/BasicFeature/Native/NativeXComponent/hvigorfile.ts b/code/BasicFeature/Native/NativeXComponent/hvigorfile.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f3cb9f1a87a81687554a76283af8df27d8bda775
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/hvigorfile.ts
@@ -0,0 +1,6 @@
+import { appTasks } from '@ohos/hvigor-ohos-plugin';
+
+export default {
+ system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
+ plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
+}
diff --git a/code/BasicFeature/Native/NativeXComponent/oh-package.json5 b/code/BasicFeature/Native/NativeXComponent/oh-package.json5
new file mode 100644
index 0000000000000000000000000000000000000000..ebdda7e54d1c41e952f1c7f6993c6d15ea3d146d
--- /dev/null
+++ b/code/BasicFeature/Native/NativeXComponent/oh-package.json5
@@ -0,0 +1,10 @@
+{
+ "modelVersion": "5.0.0",
+ "description": "Please describe the basic information.",
+ "dependencies": {
+ },
+ "devDependencies": {
+ "@ohos/hypium": "1.0.18",
+ "@ohos/hamock": "1.0.0"
+ }
+}
diff --git a/code/BasicFeature/Native/NativeXComponent/screenshots/device/changeColor.jpeg b/code/BasicFeature/Native/NativeXComponent/screenshots/device/changeColor.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..1cf3eadd18c945d30fe7d092a847fb693e488ed1
Binary files /dev/null and b/code/BasicFeature/Native/NativeXComponent/screenshots/device/changeColor.jpeg differ
diff --git a/code/BasicFeature/Native/NativeXComponent/screenshots/device/drawStar.jpeg b/code/BasicFeature/Native/NativeXComponent/screenshots/device/drawStar.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..4a75340bb29b5813dc6d827001fd06577431c35e
Binary files /dev/null and b/code/BasicFeature/Native/NativeXComponent/screenshots/device/drawStar.jpeg differ