OpenHarmony实战开发-如何实现自定义绘制 (XComponent)

XComponent组件作为一种绘制组件,通常用于满足开发者较为复杂的自定义绘制需求,例如相机预览流的显示和游戏画面的绘制。

其可通过指定其type字段来实现不同的功能,主要有两个“surface”和“component”字段可供选择。

对于“surface”类型,开发者可将相关数据传入XComponent单独拥有的“NativeWindow”来渲染画面。

对于“component”类型,主要用于实现动态加载显示内容的目的。

surface类型

XComponent设置为surface类型时,通常用于EGL/OpenGLES和媒体数据写入,并将其显示在XComponent组件上。

设置为“surface“类型时XComponent组件可以和其他组件一起进行布局和渲染。

同时XComponent又拥有单独的“NativeWindow“,可以为开发者在native侧提供native window用来创建EGL/OpenGLES环境,进而使用标准的OpenGL ES开发。

除此之外,媒体相关应用(视频、相机等)也可以将相关数据写入XComponent所提供的NativeWindow,从而呈现相应画面。

使用EGL/OpenGLES渲染

native侧代码开发要点

应用如果要通过js来桥接native,一般需要使用napi接口来处理js交互,XComponent同样不例外。

Native侧处理js逻辑的文件类型为so:

  • 每个模块对应一个so
  • so的命名规则为 lib{模块名}.so

对于使用XComponent进行标准OpenGL ES开发的场景,CMAKELists.txt文件内容大致如下:

cmake_minimum_required(VERSION 3.4.1)
project(XComponent) # 项目名称set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
# 头文件查找路径
include_directories(${NATIVERENDER_ROOT_PATH}${NATIVERENDER_ROOT_PATH}/include)# 编译目标so,SHARED表示动态库
add_library(nativerender SHAREDxxx.cpp)# 查找相关库 (包括OpenGL ES相关库和XComponent提供的ndk接口)
find_library( EGL-libEGL )find_library( GLES-libGLESv3 )find_library( libace-libace_ndk.z )# 编译so所需要的依赖
target_link_libraries(nativerender PUBLIC ${EGL-lib} ${GLES-lib} ${libace-lib} libace_napi.z.so libc++.a)
cmake_minimum_required(VERSION 3.4.1)
project(XComponent) # 项目名称set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
# 头文件查找路径
include_directories(${NATIVERENDER_ROOT_PATH}${NATIVERENDER_ROOT_PATH}/include)# 编译目标so,SHARED表示动态库
add_library(nativerender SHAREDxxx.cpp)# 查找相关库 (包括OpenGL ES相关库和XComponent提供的ndk接口)
find_library( EGL-libEGL )find_library( GLES-libGLESv3 )find_library( libace-libace_ndk.z )# 编译so所需要的依赖
target_link_libraries(nativerender PUBLIC ${EGL-lib} ${GLES-lib} ${libace-lib} libace_napi.z.so libc++.a)

Napi模块注册

static napi_value Init(napi_env env, napi_value exports)
{// 定义暴露在模块上的方法napi_property_descriptor desc[] ={// 通过DECLARE_NAPI_FUNCTION宏完成方法名的映射,这里就是将native侧的PluginRender::NapiChangeColor方法映射到ets侧的changeColor方法DECLARE_NAPI_FUNCTION("changeColor", PluginRender::NapiChangeColor),};// 通过此接口开发者可在exports上挂载native方法(即上面的PluginRender::NapiChangeColor),exports会通过js引擎绑定到js层的一个js对象NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));return exports;
}static napi_module nativerenderModule = {.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 },
};extern "C" __attribute__((constructor)) void RegisterModule(void)
{// 注册so模块napi_module_register(&nativerenderModule);
}
c++
static napi_value Init(napi_env env, napi_value exports)
{// 定义暴露在模块上的方法napi_property_descriptor desc[] ={// 通过DECLARE_NAPI_FUNCTION宏完成方法名的映射,这里就是将native侧的PluginRender::NapiChangeColor方法映射到ets侧的changeColor方法DECLARE_NAPI_FUNCTION("changeColor", PluginRender::NapiChangeColor),};// 通过此接口开发者可在exports上挂载native方法(即上面的PluginRender::NapiChangeColor),exports会通过js引擎绑定到js层的一个js对象NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));return exports;
}static napi_module nativerenderModule = {.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 },
};extern "C" __attribute__((constructor)) void RegisterModule(void)
{// 注册so模块napi_module_register(&nativerenderModule);
}

解析XComponent组件的NativeXComponent实例

NativeXComponent为XComponent提供了在native层的实例,可作为js层和native层XComponent绑定的桥梁。XComponent所提供的的NDK接口都依赖于该实例。

可以在模块被加载时的回调内(即Napi模块注册中的Init函数)解析获得NativeXComponent实例

{// ...napi_status status;napi_value exportInstance = nullptr;OH_NativeXComponent *nativeXComponent = nullptr;// 用来解析出被wrap了NativeXComponent指针的属性status = napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance);if (status != napi_ok) {return false;}// 通过napi_unwrap接口,解析出NativeXComponent的实例指针status = napi_unwrap(env, exportInstance, reinterpret_cast<void**>(&nativeXComponent));// ...
}
c++
{// ...napi_status status;napi_value exportInstance = nullptr;OH_NativeXComponent *nativeXComponent = nullptr;// 用来解析出被wrap了NativeXComponent指针的属性status = napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance);if (status != napi_ok) {return false;}// 通过napi_unwrap接口,解析出NativeXComponent的实例指针status = napi_unwrap(env, exportInstance, reinterpret_cast<void**>(&nativeXComponent));// ...
}

注册XComponent事件回调

依赖解析XComponent组件的NativeXComponent实例拿到的NativeXComponent指针,通过OH_NativeXComponent_RegisterCallback接口进行回调注册。一般的会在模块被加载时的回调内(即Napi模块注册中的Init函数)进行回调注册。

{...OH_NativeXComponent *nativeXComponent = nullptr;// 解析出NativeXComponent实例OH_NativeXComponent_Callback callback;callback->OnSurfaceCreated = OnSurfaceCreatedCB; // surface创建成功后触发,开发者可以从中获取native window的句柄callback->OnSurfaceChanged = OnSurfaceChangedCB; // surface发生变化后触发,开发者可以从中获取native window的句柄以及XComponent的变更信息callback->OnSurfaceDestroyed = OnSurfaceDestroyedCB; // surface销毁时触发,开发者可以在此释放资源callback->DispatchTouchEvent = DispatchTouchEventCB; // XComponent的touch事件回调接口,开发者可以从中获得此次touch事件的信息OH_NativeXComponent_RegisterCallback(nativeXComponent, callback);...
}
c++
{...OH_NativeXComponent *nativeXComponent = nullptr;// 解析出NativeXComponent实例OH_NativeXComponent_Callback callback;callback->OnSurfaceCreated = OnSurfaceCreatedCB; // surface创建成功后触发,开发者可以从中获取native window的句柄callback->OnSurfaceChanged = OnSurfaceChangedCB; // surface发生变化后触发,开发者可以从中获取native window的句柄以及XComponent的变更信息callback->OnSurfaceDestroyed = OnSurfaceDestroyedCB; // surface销毁时触发,开发者可以在此释放资源callback->DispatchTouchEvent = DispatchTouchEventCB; // XComponent的touch事件回调接口,开发者可以从中获得此次touch事件的信息OH_NativeXComponent_RegisterCallback(nativeXComponent, callback);...
}

创建EGL/OpenGLES环境

在注册的OnSurfaceCreated回调中,开发者能拿到native window的句柄(其本质就是XComponent所单独拥有的NativeWindow),因此可以在这里创建应用自己的EGL/OpenGLES开发环境,由此开始具体渲染逻辑的开发。

EGLCore* eglCore_; // EGLCore为封装了OpenGL相关接口的类
uint64_t width_;
uint64_t height_;
void OnSurfaceCreatedCB(OH_NativeXComponent* component, void* window)
{int32_t ret = OH_NativeXComponent_GetXComponentSize(component, window, &width_, &height_);if (ret === OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {eglCore_->GLContextInit(window, width_, height_); // 初始化OpenGL环境}
}
c++
EGLCore* eglCore_; // EGLCore为封装了OpenGL相关接口的类
uint64_t width_;
uint64_t height_;
void OnSurfaceCreatedCB(OH_NativeXComponent* component, void* window)
{int32_t ret = OH_NativeXComponent_GetXComponentSize(component, window, &width_, &height_);if (ret === OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {eglCore_->GLContextInit(window, width_, height_); // 初始化OpenGL环境}
}

ArkTS侧语法介绍

开发者在ArkTS侧使用如下代码,即可用XComponent组件进行利用EGL/OpenGLES渲染的开发。

XComponent({ id: 'xcomponentId1', type: 'surface', libraryname: 'nativerender' }).onLoad((context) => {}).onDestroy(() => {})
  • id :与XComponent组件为一一对应关系,不建议重复。通常开发者可以在native侧通过OH_NativeXComponent_GetXComponentId接口来获取对应的id从而绑定对应的XComponent。

说明:

如果id重复,在native侧将无法对多个XComponent进行区分。

  • libraryname:加载模块的名称,必须与在native侧Napi模块注册时nm_modname的名字一致。

说明:

应用加载模块实现跨语言调用有两种方式:

1.使用NAPI的import方式加载:

import nativerender from "libnativerender.so"

2.使用XComponent组件加载,本质也是使用了NAPI机制来加载。 该加载方式和import加载方式的区别在于,在加载动态库时会将XComponent的NativeXComponent实例暴露到应用的native层中,从而让开发者可以使用XComponent的NDK接口。

  • onLoad事件
  • 触发时刻:XComponent准备好surface后触发。
  • 参数context:其上面挂载了暴露在模块上的native方法,使用方法类似于利用 import context2 from“libnativerender.so” 直接加载模块后获得的context2实例。
  • 时序:onLoad事件的触发和Surface相关,其和native侧的OnSurfaceCreated的时序如下图:

图片2

  • onDestroy事件

触发时刻:XComponent组件被销毁时触发与一般ArkUI的组件销毁时机一致,其和native侧的OnSurfaceDestroyed的时序如下图:

图片3

媒体数据写入

XComponent所持有的NativeWindow符合“生产者-消费者”模型。

Camera、AVPlayer等符合生产者设计的部件都可以将数据写入XComponent持有的NativeWindow并通过XComponent显示。

图片1

开发者可通过绑定XComponentController获得对应XComponent的surfaceId(该id可以唯一确定一个surface),从而传给相应的部件接口。

class suf{surfaceId:string = "";mXComponentController: XComponentController = new XComponentController();set(){this.surfaceId = this.mXComponentController.getXComponentSurfaceId()}
}
@State surfaceId:string = "";
mXComponentController: object = new XComponentController();
XComponent({ id: '', type: 'surface', controller: this.mXComponentController }).onLoad(() => {let sufset = new suf()sufset.set()})

component类型

XComponent设置为component类型时通常用于在XComponent内部执行非UI逻辑以实现动态加载显示内容的目的。

说明:

type为"component"时,XComponent作为容器,子组件沿垂直方向布局:

  • 垂直方向上对齐格式:FlexAlign.Start
  • 水平方向上对齐格式:FlexAlign.Center

不支持所有的事件响应。

布局方式更改和事件响应均可通过挂载子组件来设置。

内部所写的非UI逻辑需要封装在一个或多个函数内。

场景示例

@Builder
function addText(label: string): void {Text(label).fontSize(40)
}@Entry
@Component
struct Index {@State message: string = 'Hello XComponent'@State messageCommon: string = 'Hello World'build() {Row() {Column() {XComponent({ id: 'xcomponentId-container', type: 'component' }) {addText(this.message)Divider().margin(4).strokeWidth(2).color('#F1F3F5').width("80%")Column() {Text(this.messageCommon).fontSize(30)}}}.width('100%')}.height('100%')}
}

在这里插入图片描述

如果大家还没有掌握鸿蒙,现在想要在最短的时间里吃透它,我这边特意整理了《鸿蒙语法ArkTS、TypeScript、ArkUI等…视频教程》以及《鸿蒙开发>鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

鸿蒙语法ArkTS、TypeScript、ArkUI等…视频教程:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

在这里插入图片描述

OpenHarmony APP开发教程步骤:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

在这里插入图片描述

鸿蒙开发>鸿蒙开发学习手册》:

如何快速入门:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

1.基本概念
2.构建第一个ArkTS应用
3.……

在这里插入图片描述

开发基础知识:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

在这里插入图片描述

基于ArkTS 开发:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

在这里插入图片描述

鸿蒙生态应用开发白皮书V2.0PDF:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

在这里插入图片描述


http://www.ppmy.cn/devtools/21857.html

相关文章

RT-Thread之线程管理(线程的基础概念和使用)

文章目录 前言一、RT-Thread线程的概念二、线程的创建与删除2.1用户线程和系统线程2.2线程控制块2.3线程栈2.4入口函数 三、线程的创建和启动3.1线程创建的种类3.2动态创建线程3.3静态创建线程 总结 前言 本篇文章来给大家讲解RT-Thread中的线程管理&#xff0c;线程管理是属于…

vue前端实现下载文件功能

首先介绍一下我使用a标签方法碰到的错误&#xff1a; 点击下载后一直显示无法下载&#xff0c;更换浏览器也不行&#xff0c;后来找到了错误所在。 错误原因就是我把路径写在与我存图片的路径目录下面了 src/assets/... ,这样是不对的&#xff0c;应该把你需要下载的文件放在…

生成扩散模型漫谈:信噪比与大图生成(下)

©PaperWeekly 原创 作者 | 苏剑林 单位 | 科学空间 研究方向 | NLP、神经网络 上一篇文章《生成扩散模型漫谈&#xff1a;信噪比与大图生成&#xff08;上&#xff09;》中&#xff0c;我们介绍了通过对齐低分辨率的信噪比来改进 noise schedule&#xff0c;从而改善直接…

Blender边操作

1.边的细分 Subdivide -选中一条边&#xff0c;右键&#xff0c;细分 2.边的滑移&#xff0c;Edge Slide -选中一条边 -菜单&#xff0c;边-滑移边线 其中&#xff0c;滑移时&#xff0c;是以两侧的邻边为轨道&#xff0c;滑移的边线无法越过轨道尽头 3.边的删除 -选中一…

一个简单的springcloud案例

使用的组件&#xff1a;Eureka、Ribbon、Feign、Hystrix 首先创建一个maven父工程&#xff0c;并提供pom 在 这个pom中指定了springcloud版本以及springboot的版本 <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/…

[Java EE] 多线程(三):线程安全问题(上)

1. 线程安全 1.1 线程安全的概念 如果多线程环境下代码运行的结果不符合我们的预期,则我们说存在线程安全问题,即程序存在bug,反之,不存在线程安全问题. 1.2 线程不安全的原因 我们下面举出一个线程不安全的例子:我们想要在两个线程中对count进行操作 public class Demo9 …

【数据结构】单链表的尾插法

尾插法是一种在链表末尾插入新元素的方法&#xff0c;它的核心思想是保持链表的尾部指针&#xff08;或称为尾节点&#xff09;&#xff0c;这样可以在常数时间内完成尾部插入操作。尾插法的主要步骤如下&#xff1a; 创建新节点&#xff1a;首先&#xff0c;根据需要插入的数据…

Ansible自动化运维工具主机清单配置

作者主页&#xff1a;点击&#xff01; Ansible专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年4月24日12点21分 Ansible主机清单文件用于定义要管理的主机及其相关信息。它是Ansible的核心配置文件之一&#xff0c;用于Ansible识别目标主机并与其建立连接。 …

什么是堆?什么是栈?他们之间从区别和联系

在JavaScript&#xff08;以及大多数现代编程语言&#xff09;中&#xff0c;内存被分为几个不同的部分&#xff0c;用于存储不同类型的数据和执行不同的操作。其中&#xff0c;堆&#xff08;Heap&#xff09;和栈&#xff08;Stack&#xff09;是两个最重要的概念。 栈&…

云渲染一张图多少钱

使用云渲染渲染一张效果图的价格没法确定多少钱一张&#xff0c;云渲染一张图的价格会受到多个因素的影响&#xff0c;如云渲染平台的定价策略、所选的渲染配置、优惠政策以及你提交的场景任务等。因此&#xff0c;无法给出确切的单一价格。 不同的云渲染平台会有不同的定价模…

基于CANoe从零创建以太网诊断工程(2)—— TCP/IP Stack 配置的三种选项

&#x1f345; 我是蚂蚁小兵&#xff0c;专注于车载诊断领域&#xff0c;尤其擅长于对CANoe工具的使用&#x1f345; 寻找组织 &#xff0c;答疑解惑&#xff0c;摸鱼聊天&#xff0c;博客源码&#xff0c;点击加入&#x1f449;【相亲相爱一家人】&#x1f345; 玩转CANoe&…

ZooKeeper的分布式锁

ZooKeeper的分布式锁机制主要利用ZooKeeper的节点特性&#xff0c;通过创建和删除节点来实现锁的控制。 实现步骤&#xff1a; 创建锁节点&#xff1a;当一个进程需要访问共享资源时&#xff0c;它会在ZooKeeper中创建一个唯一的临时顺序节点作为锁。尝试获取锁&#xff1a;进…

Swift - Hello World

文章目录 Swift - Hello World1. Hello World Swift - Hello World 1. Hello World 不用编写main函数&#xff0c;Swift将全局范围内的首句可执行代码作为程序入口一句代码尾部可以省略分号&#xff08;;&#xff09;&#xff0c;多句代码写到同一行时必须用分号&#xff08;…

学习Python的第二天:深化理解,编程实践

在探索Python编程的第二天&#xff0c;我们将通过实践案例来深化对前一天学习内容的理解&#xff0c;并进一步提升我们的编程能力。 首先&#xff0c;回顾一下第一天学习的内容&#xff0c;我们掌握了Python的基本语法、变量和数据类型。接下来&#xff0c;我们将通过编写一个…

diskMirror-backEnd-spring-boot | diskMirror 后端服务器 SpringBoot 版本!

diskMirror-backEnd-spring-boot diskMirror 后端服务器的 SpringBoot 版本&#xff0c;此版本中拓展了 DiskMirrorBackEnd&#xff0c;是一个完全的SpringBoot项目&#xff01; 目录 文章目录 diskMirror-backEnd-spring-boot目录我如何部署与配置docker 方式部署 diskMirro…

Blender面操作

1.细分Subdivide -选择一个面 -右键&#xff0c;细分 -微调&#xff0c;设置切割次数 2.删除 -选择一个或多个面&#xff0c;按X键 -选择要删除的是面&#xff0c;线还是点 3.挤出面Extrude -选择一个面 -Extrude工具 -拖拽手柄&#xff0c;向外挤出 -微调&#xff…

【C++杂货铺】二叉搜索树

目录 &#x1f308;前言&#x1f308; &#x1f4c1; 二叉搜索树的概念 &#x1f4c1; 二叉搜索树的操作 &#x1f4c2; 二叉搜索树的查找 &#x1f4c2; 二叉搜索树的插入 &#x1f4c2; 二叉搜书树的删除 &#x1f4c1; 二叉搜索树的应用 &#x1f4c1; 二叉搜索树的…

Gateway服务网关!!!

一、为什么需要服务网关&#xff1a; 两大特性&#xff1a;高可用和高性能 1、高性能&#xff1a;采用异步的方式调用服务。 2、高可用 二、网关包含三大属性&#xff1a; 三、基本配置 <dependency><groupId>org.springframework.boot</groupId><artif…

git .gitignore忽略非必要文件提交

1 简介 对于经常使用Git的朋友来说&#xff0c;.gitignore配置一定不会陌生。这种方式通过在项目的某个文件夹下定义.gitignore文件&#xff0c;在该文件中定义相应的忽略规则&#xff0c;来管理当前文件夹下的文件的Git提交行为。 .gitignore文件是可以提交到公有仓库中&…

算法——双指针

双指针经常服务于需要一边遍历数组&#xff0c;一边对数组元素进行改动的题目&#xff0c;有些人也称双指针为快慢指针。 同时双指针只是一种思想&#xff0c;实际做题时并不一定会真的采用指针。 移动零 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾…