文档管理中心

自定义渲染 (XComponent)

概述

XComponent组件作为一种渲染组件,可用于EGL/OpenGLES和媒体数据写入,通过使用XComponent持有的“NativeWindow”渲染画面,满足开发需要实现高级自定义渲染的需求,例如相机预览流的显示和游戏画面的渲染。开发者可通过指定XComponent组件的type字段来实现不同的渲染方式,分别为XComponentType.SURFACE和XComponentType.TEXTURE。对于SURFACE类型,开发者将定制的绘制内容单独展示到屏幕上。对于TEXTURE类型,开发者将定制的绘制内容和XComponent组件的内容合成后展示到屏幕上。

可以将XComponent类比为一个“画布”,在其上支持使用各种渲染技术(如OpenGL、Vulkan等)绘制复杂的图形,而XComponent组件则负责管理这个画布的位置、大小和各种交互事件。

目前XComponent主要用于两类场景:

展开
场景类型 使用场景
高性能渲染 游戏画面、3D图形、复杂动画等。
媒体数据处理 相机预览、视频播放、图像处理等。

约束与限制

当开发者传输的绘制内容包含透明元素时,Surface区域的显示效果会与下方内容进行合成展示。例如,若传输的内容完全透明,且XComponent的背景色被设置为黑色,同时Surface保持默认的大小与位置,则最终显示的将是一片黑色区域。

XComponent渲染上屏原理

XComponent持有一个Surface,开发者能通过调用NativeWindow等接口,申请并提交Buffer至图形队列,以此方式将自绘制内容传送至该Surface,其主体流程如下:

应用RequestBuffer获取空闲帧 → 应用生产帧数据 → 应用调用FlushBuffer提交到BufferQueue → 系统渲染侧通过AcquireBuffer获取帧 → 渲染到屏幕 → 系统渲染侧通过调用ReleaseBuffer释放。

经过上述流程,应用自绘制的内容就可以显示在XComponent持有的Surface区域,而XComponent则负责将此Surface整合进UI界面,其中展示的内容正是开发者发送的自绘制内容。Surface的默认位置与大小与XComponent组件一致,开发者可利用setXComponentSurfaceRect接口自定义调整Surface的位置和大小。XComponent组件负责创建Surface,并通过回调将Surface的相关信息告知应用。应用可以通过一系列接口设定Surface的属性。该组件本身不对所绘制的内容进行感知,亦不提供渲染绘制的接口。

说明

主体流程中提到需要应用进行的RequestBuffer和FlushBuffer操作,在具体场景下一般已经被相关API(如相机模块、播放器模块、OpenGL相关接口等)封装,应用的实际开发者只需按要求调用这些API即可,不需要直接操作BufferQueue。

创建XComponent和管理Surface生命周期

为满足开发者的各种需求,XComponent组件提供了多种创建方式以及多种Surface生命周期的管理方式,下面将进行介绍。

创建XComponent

目前ArkUI提供了三种UI组件的创建方式,分别是使用ArkTS声明式UI描述创建、使用ArkTS自定义组件节点创建以及使用NDK接口创建。

通用UI界面开发场景下,建议使用ArkTS声明式UI描述创建XComponent组件。对于需要使用ArkTS自定义组件节点创建以及NDK接口创建的具体场景请参考这两种创建方式的相关介绍。

管理XComponent持有Surface的生命周期

XComponent渲染上屏原理中提到,XComponent能够显示应用自绘制的内容依赖的是其持有的Surface,因此了解如何获取XComponent持有的Surface的生命周期也十分重要。

XComponent推荐使用两种方式获取XComponent持有Surface的生命周期,分别为在ArkTS侧使用XComponentController管理Surface生命周期,以及在Native侧使用OH_ArkUI_SurfaceHolder管理Surface生命周期。

对于需要在ArkTS侧使用已封装接口进行功能开发(如相机预览、视频播放等)或对跨语言性能损耗不敏感的跨语言开发,建议直接在ArkTS侧使用XComponentController管理Surface生命周期。其生命周期的触发时机如下:

  • onSurfaceCreated回调

    触发时刻:XComponent创建完成且创建好Surface后触发。

    ArkTS侧onSurfaceCreated的时序如下图:

  • onSurfaceChanged回调

    触发时刻:Surface大小变化触发重新布局之后触发。

    ArkTS侧onSurfaceChanged的时序如下图:

  • onSurfaceDestroyed回调

    触发时刻:XComponent组件被销毁时触发,与一般ArkUI的组件销毁时机一致。

    ArkTS侧onSurfaceDestroyed的时序图:

对于复杂的交互逻辑需跨语言开发,追求极致渲染性能或业务需求自主控制Surface的创建和销毁的,建议在Native侧使用OH_ArkUI_SurfaceHolder管理Surface生命周期。其生命周期触发时机如下:

  • OnSurfaceCreated回调

    触发时刻:XComponent创建完成且Surface绑定生命周期回调后,满足以下任一条件时触发。

    1. 组件上树且autoInitialize = true。
    2. 调用OH_ArkUI_XComponent_Initialize

    Native侧OnSurfaceCreated的时序如下图:

  • OnSurfaceChanged回调

    触发时刻:OnSurfaceCreated回调成功触发且Surface大小变化触发重新布局之后触发。

    Native侧OnSurfaceChanged的时序如下图:

  • OnSurfaceDestroyed回调

    触发时刻:组件下树且autoInitialize=true 或者调用OH_ArkUI_XComponent_Finalize后触发。

    Native侧OnSurfaceDestroyed的时序图:

XComponent的开发范式

创建XComponent管理XComponent持有Surface的生命周期进行排列组合,除使用NDK接口创建的XComponent无法在ArkTS侧使用XComponentController来管理Surface生命周期外,目前共有以下五种XComponent开发范式:

  • 通过ArkTS声明式UI描述来创建组件并结合XComponentController实现对Surface生命周期的管理。

    收起
    自动换行
    深色代码主题
    复制
    1. import nativeRender from 'libnativerender.so';
    2. // 重写XComponentController,设置生命周期回调
    3. class MyXComponentController extends XComponentController{
    4. onSurfaceCreated(surfaceId: string): void {
    5. console.info(`onSurfaceCreated surfaceId: ${surfaceId}`);
    6. nativeRender.SetSurfaceId(BigInt(surfaceId));
    7. }
    8. onSurfaceChanged(surfaceId: string, rect: SurfaceRect): void {
    9. console.info(`onSurfaceChanged surfaceId: ${surfaceId}, rect: ${JSON.stringify(rect)}`);
    10. // 在onSurfaceChanged中调用ChangeSurface绘制内容
    11. nativeRender.ChangeSurface(BigInt(surfaceId), rect.surfaceWidth, rect.surfaceHeight);
    12. }
    13. onSurfaceDestroyed(surfaceId: string): void {
    14. console.info(`onSurfaceDestroyed surfaceId: ${surfaceId}`);
    15. nativeRender.DestroySurface(BigInt(surfaceId));
    16. }
    17. }
    18. @Entry
    19. @Component
    20. struct Index {
    21. @State currentStatus: string = 'index';
    22. xComponentController: XComponentController = new MyXComponentController();
    23. build() {
    24. Column() {
    25. // ···
    26. //在xxx.ets 中定义 XComponent
    27. Column({ space: 10 }) {
    28. XComponent({
    29. type: XComponentType.SURFACE,
    30. controller: this.xComponentController
    31. })
    32. Text(this.currentStatus)
    33. .fontSize('24fp')
    34. .fontWeight(500)
    35. }
    36. .onClick(() => {
    37. let surfaceId = this.xComponentController.getXComponentSurfaceId();
    38. nativeRender.ChangeColor(BigInt(surfaceId));
    39. let hasChangeColor: boolean = false;
    40. if (nativeRender.GetXComponentStatus(BigInt(surfaceId))) {
    41. hasChangeColor = nativeRender.GetXComponentStatus(BigInt(surfaceId)).hasChangeColor;
    42. }
    43. if (hasChangeColor) {
    44. this.currentStatus = 'change color';
    45. }
    46. })
    47. // ···
    48. }
    49. .width('100%')
    50. .height('100%')
    51. }
    52. }
  • 通过ArkTS声明式UI描述来创建组件并结合OH_ArkUI_SurfaceHolder实现对Surface生命周期的管理。

    收起
    自动换行
    深色代码主题
    复制
    1. import native from 'libnativerender.so';
    2. // ...
    3. @Component
    4. export struct SurfaceHolderDeclarative {
    5. @State currentStatus: string = 'init';
    6. private xcNode: FrameNode | null = null;
    7. build() {
    8. NavDestination() {
    9. // ...
    10. Column({ space: 10 }) {
    11. // 创建XComponent组件
    12. XComponent({
    13. type: XComponentType.SURFACE,
    14. })
    15. .id('XComponentSurfaceHolder')
    16. .onAttach(() => {
    17. this.xcNode = this.getUIContext().getAttachedFrameNodeById('XComponentSurfaceHolder');
    18. if (!this.xcNode) {
    19. return;
    20. }
    21. native.bindNode('XComponentSurfaceHolder', this.xcNode); // 跨语言调用至Native侧获取SurfaceHolder并绑定Surface生命周期回调
    22. this.currentStatus = 'index';
    23. })
    24. .onDetach(() => {
    25. native.unbindNode('XComponentSurfaceHolder');
    26. this.xcNode = null;
    27. })
    28. // ...
    29. }
    30. // ...
    31. }
    32. }
    33. }

    Native侧获取SurfaceHolder并绑定Surface生命周期回调的具体实现。

    收起
    自动换行
    深色代码主题
    复制
    1. napi_value PluginManager::BindNode(napi_env env, napi_callback_info info)
    2. {
    3. size_t argc = 2;
    4. napi_value args[2] = {nullptr};
    5. napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    6. std::string nodeId = value2String(env, args[0]);
    7. ArkUI_NodeHandle handle;
    8. OH_ArkUI_GetNodeHandleFromNapiValue(env, args[1], &handle); // 获取nodeHandle
    9. OH_ArkUI_SurfaceHolder *holder = OH_ArkUI_SurfaceHolder_Create(handle); // 获取SurfaceHolder
    10. nodeHandleMap_[nodeId] = handle;
    11. surfaceHolderMap_[handle] = holder;
    12. auto callback = OH_ArkUI_SurfaceCallback_Create(); // 创建SurfaceCallback
    13. callbackMap_[holder] = callback;
    14. auto render = new EGLRender();
    15. OH_ArkUI_SurfaceHolder_SetUserData(holder, render); // 将render保存在holder中
    16. OH_ArkUI_SurfaceCallback_SetSurfaceCreatedEvent(callback, OnSurfaceCreatedNative); // 注册OnSurfaceCreated回调
    17. OH_ArkUI_SurfaceCallback_SetSurfaceChangedEvent(callback, OnSurfaceChangedNative); // 注册OnSurfaceChanged回调
    18. OH_ArkUI_SurfaceCallback_SetSurfaceDestroyedEvent(callback, OnSurfaceDestroyedNative); // 注册OnSurfaceDestroyed回调
    19. OH_ArkUI_SurfaceHolder_AddSurfaceCallback(holder, callback); // 注册SurfaceCallback回调
    20. // ...
    21. return nullptr;
    22. }
  • 通过ArkTS自定义组件节点来创建组件并结合XComponentController实现对Surface生命周期的管理。

    收起
    自动换行
    深色代码主题
    复制
    1. // 重写XComponentController,设置生命周期回调
    2. class MyXComponentController extends XComponentController {
    3. onSurfaceCreated(surfaceId: string): void {
    4. console.info(`onSurfaceCreated surfaceId: ${surfaceId}`);
    5. }
    6. onSurfaceChanged(surfaceId: string, rect: SurfaceRect): void {
    7. console.info(`onSurfaceChanged surfaceId: ${surfaceId}, rect: ${JSON.stringify(rect)}`);
    8. }
    9. onSurfaceDestroyed(surfaceId: string): void {
    10. console.info(`onSurfaceDestroyed surfaceId: ${surfaceId}`);
    11. }
    12. }
    13. class MyNodeController extends NodeController {
    14. public xComponent: typeNode.XComponent | undefined = undefined;
    15. public xComponentId: string = 'xcp' + (new Date().getTime());
    16. public node: FrameNode | undefined = undefined;
    17. public column: typeNode.Column | undefined = undefined;
    18. private xcController: MyXComponentController = new MyXComponentController();
    19. makeNode(uiContext: UIContext): FrameNode | null {
    20. this.node = new FrameNode(uiContext);
    21. this.column = typeNode.createNode(uiContext, 'Column')
    22. this.column.initialize()
    23. .width('100%')
    24. .height('100%')
    25. try {
    26. this.node.appendChild(this.column);
    27. } catch (error) {
    28. console.error('Fail to append child: ', error);
    29. }
    30. // 创建XComponent组件节点,并绑定XComponentController
    31. this.xComponent =
    32. typeNode.createNode(uiContext, 'XComponent', { type: XComponentType.SURFACE, controller: this.xcController });
    33. this.xComponent.attribute
    34. try {
    35. this.column.appendChild(this.xComponent);
    36. } catch (error) {
    37. console.error('Fail to append child: ', error);
    38. }
    39. return this.node;
    40. }
    41. }
  • 通过ArkTS自定义组件节点来创建组件并结合OH_ArkUI_SurfaceHolder实现对Surface生命周期的管理。

    收起
    自动换行
    深色代码主题
    复制
    1. import native from 'libnativerender.so';
    2. import { FrameNode, NodeController, typeNode, UIContext } from '@kit.ArkUI';
    3. class MyNodeController extends NodeController {
    4. // ...
    5. makeNode(uiContext: UIContext): FrameNode | null {
    6. // ...
    7. // 创建XComponent组件节点
    8. this.xComponent = typeNode.createNode(uiContext, 'XComponent', { type: XComponentType.SURFACE });
    9. this.xComponent.attribute
    10. .id(this.xComponentId)
    11. .focusable(true)
    12. .focusOnTouch(true)
    13. native.bindNode(this.xComponentId, this.xComponent)
    14. // ...
    15. }
    16. // ...
    17. }
    18. // ...
    19. @Component
    20. export struct SurfaceHolderTypeNode {
    21. // ...
    22. myNodeController: MyNodeController = new MyNodeController();
    23. build() {
    24. NavDestination() {
    25. Column() {
    26. // ...
    27. Column() {
    28. if (this.isShow) {
    29. NodeContainer(this.myNodeController)
    30. .width(200)
    31. .height(200)
    32. .focusable(true)
    33. .focusOnTouch(true)
    34. .defaultFocus(true)
    35. }
    36. }.height(200)
    37. // ...
    38. }
    39. .width('100%')
    40. }
    41. }
    42. }

    Native侧绑定Surface生命周期回调的具体实现。

    收起
    自动换行
    深色代码主题
    复制
    1. napi_value PluginManager::BindNode(napi_env env, napi_callback_info info)
    2. {
    3. size_t argc = 2;
    4. napi_value args[2] = {nullptr};
    5. napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    6. std::string nodeId = value2String(env, args[0]);
    7. ArkUI_NodeHandle handle;
    8. OH_ArkUI_GetNodeHandleFromNapiValue(env, args[1], &handle); // 获取nodeHandle
    9. OH_ArkUI_SurfaceHolder *holder = OH_ArkUI_SurfaceHolder_Create(handle); // 获取SurfaceHolder
    10. nodeHandleMap_[nodeId] = handle;
    11. surfaceHolderMap_[handle] = holder;
    12. auto callback = OH_ArkUI_SurfaceCallback_Create(); // 创建SurfaceCallback
    13. callbackMap_[holder] = callback;
    14. auto render = new EGLRender();
    15. OH_ArkUI_SurfaceHolder_SetUserData(holder, render); // 将render保存在holder中
    16. OH_ArkUI_SurfaceCallback_SetSurfaceCreatedEvent(callback, OnSurfaceCreatedNative); // 注册OnSurfaceCreated回调
    17. OH_ArkUI_SurfaceCallback_SetSurfaceChangedEvent(callback, OnSurfaceChangedNative); // 注册OnSurfaceChanged回调
    18. OH_ArkUI_SurfaceCallback_SetSurfaceDestroyedEvent(callback, OnSurfaceDestroyedNative); // 注册OnSurfaceDestroyed回调
    19. OH_ArkUI_SurfaceHolder_AddSurfaceCallback(holder, callback); // 注册SurfaceCallback回调
    20. // ...
    21. return nullptr;
    22. }
  • 通过NDK接口来创建组件并使用OH_ArkUI_SurfaceHolder实现对Surface生命周期的管理。

    收起
    自动换行
    深色代码主题
    复制
    1. import nativeNode from 'libnativerender.so';
    2. import { NodeContent } from '@kit.ArkUI';
    3. @Component
    4. export struct SurfaceHolderNDK {
    5. @State currentStatus: string = 'init';
    6. private nodeContent: NodeContent = new NodeContent();
    7. aboutToAppear(): void {
    8. nativeNode.createNativeNode(this.nodeContent, 'SurfaceHolderNDK');
    9. this.currentStatus = 'index'
    10. }
    11. build() {
    12. NavDestination() {
    13. Column() {
    14. // ...
    15. Column({ space: 10 }) {
    16. ContentSlot(this.nodeContent);
    17. // ...
    18. }
    19. // ...
    20. }
    21. .width('100%')
    22. .height('100%')
    23. }
    24. }
    25. }

    Native侧createNativeNode可以参照如下代码实现。

    收起
    自动换行
    深色代码主题
    复制
    1. napi_value PluginManager::createNativeNode(napi_env env, napi_callback_info info)
    2. {
    3. // ...
    4. ArkUI_NodeContentHandle nodeContentHandle_ = nullptr;
    5. OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &nodeContentHandle_);
    6. nodeAPI = reinterpret_cast<ArkUI_NativeNodeAPI_1 *>(
    7. OH_ArkUI_QueryModuleInterfaceByName(ARKUI_NATIVE_NODE, "ArkUI_NativeNodeAPI_1"));
    8. std::string tag = value2String(env, args[1]);
    9. // ...
    10. if (nodeAPI != nullptr && nodeAPI->createNode != nullptr && nodeAPI->addChild != nullptr) {
    11. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "CreateNativeNode tag=%{public}s",
    12. tag.c_str());
    13. auto nodeContentEvent = [](ArkUI_NodeContentEvent *event) {
    14. ArkUI_NodeContentHandle handle = OH_ArkUI_NodeContentEvent_GetNodeContentHandle(event);
    15. std::string *userData = reinterpret_cast<std::string *>(OH_ArkUI_NodeContent_GetUserData(handle));
    16. if (!userData) {
    17. return;
    18. }
    19. if (OH_ArkUI_NodeContentEvent_GetEventType(event) != NODE_CONTENT_EVENT_ON_ATTACH_TO_WINDOW) {
    20. return;
    21. }
    22. ArkUI_NodeHandle testNode;
    23. if (userData->find("SurfaceHolder") == std::string::npos) {
    24. // ...
    25. } else {
    26. // 创建XComponent组件并使用SurfaceHolder管理Surface生命周期
    27. testNode = CreateNodeHandleUsingSurfaceHolder(*userData);
    28. }
    29. delete userData;
    30. userData = nullptr;
    31. OH_ArkUI_NodeContent_AddNode(handle, testNode);
    32. };
    33. OH_ArkUI_NodeContent_RegisterCallback(nodeContentHandle_, nodeContentEvent);
    34. }
    35. return nullptr;
    36. }

    创建XComponent组件并使用SurfaceHolder管理Surface生命周期的实现如下。

    收起
    自动换行
    深色代码主题
    复制
    1. ArkUI_NodeHandle CreateNodeHandleUsingSurfaceHolder(const std::string &tag)
    2. {
    3. ArkUI_NodeHandle column = nodeAPI->createNode(ARKUI_NODE_COLUMN);
    4. // ...
    5. xc = nodeAPI->createNode(ARKUI_NODE_XCOMPONENT); // 创建XComponent节点
    6. // ...
    7. OH_ArkUI_SurfaceHolder *holder = OH_ArkUI_SurfaceHolder_Create(xc); // 获取SurfaceHolder
    8. PluginManager::surfaceHolderMap_[xc] = holder;
    9. PluginManager::nodeHandleMap_[tag] = xc;
    10. auto callback = OH_ArkUI_SurfaceCallback_Create(); // 创建SurfaceCallback
    11. PluginManager::callbackMap_[holder] = callback;
    12. auto render = new EGLRender();
    13. OH_ArkUI_SurfaceHolder_SetUserData(holder, render); // 将render保存在holder中
    14. OH_ArkUI_SurfaceCallback_SetSurfaceCreatedEvent(callback, OnSurfaceCreatedNative); // 注册OnSurfaceCreated回调
    15. OH_ArkUI_SurfaceCallback_SetSurfaceChangedEvent(callback, OnSurfaceChangedNative); // 注册OnSurfaceChanged回调
    16. OH_ArkUI_SurfaceCallback_SetSurfaceDestroyedEvent(callback, OnSurfaceDestroyedNative); // 注册OnSurfaceDestroyed回调
    17. OH_ArkUI_SurfaceHolder_AddSurfaceCallback(holder, callback); // 添加SurfaceCallback回调
    18. if (nodeAPI->addNodeEventReceiver(xc, onEvent)) { // 添加事件监听,返回0表示成功,非0表示失败
    19. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "addNodeEventReceiver error");
    20. }
    21. if (nodeAPI->registerNodeEvent(xc, NODE_TOUCH_EVENT, 0, nullptr)) { // 用C接口注册touch事件,返回0表示成功,非0表示失败
    22. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "registerTouchEvent error");
    23. }
    24. nodeAPI->addChild(column, xc); // 将XComponent挂载到Column下
    25. return column;
    26. }

OH_NativeXComponent向OH_ArkUI_SurfaceHolder的迁移

从API version 8开始,开发者可以通过基于OH_NativeXComponent实例相关的接口进行XComponent组件Surface的生命周期监听、获取NativeWindow实例以及监听基础事件,实现渲染绘制和响应交互功能。但使用OH_NativeXComponent相关的接口存在以下问题:

  • OH_NativeXComponent实例生命周期与XComponent组件强相关,开发者如果在XComponent组件销毁后仍然操作该对象将可能出现稳定性问题,造成应用的崩溃。
  • OH_NativeXComponent提供的交互事件接口不够丰富,只提供基础的触摸、鼠标、键盘交互接口,开发者若想识别长按、拖拽等高级手势需要自己写识别逻辑。

基于上述问题,建议使用OH_ArkUI_SurfaceHolder相关接口代替OH_NativeXComponent相关接口,以下以使用ArkTS声明式UI描述创建组件为例,介绍如何将使用OH_NativeXComponent管理Surface生命周期切换为使用OH_ArkUI_SurfaceHolder管理Surface生命周期。

组件创建

组件创建过程中的主要差异在于使用OH_NativeXComponent需要传入id和libraryname属性以支持在Native侧获取对应的OH_NativeXComponent实例;而使用OH_ArkUI_SurfaceHolder管理Surface生命周期的XComponent不再需要在XComponent的构造参数中传入id和libraryname属性,而是直接将组件对应的FrameNode节点传递至Native侧进行生命周期绑定和其他设置。

  • OH_NativeXComponent

    收起
    自动换行
    深色代码主题
    复制
    1. XComponent({
    2. id: 'xcomponentId',
    3. type: XComponentType.SURFACE,
    4. libraryname: 'nativerender' // 利用id和libraryname属性在Native侧获取NativeXcomponent并绑定Surface生命周期
    5. })
    6. .onLoad((xComponentContext) => {
    7. this.xComponentContext = xComponentContext as XComponentContext;
    8. this.currentStatus = 'index';
    9. })
    10. .onDestroy(() => {
    11. console.info('onDestroy');
    12. })
    13. .id('xcomponent')
  • OH_ArkUI_SurfaceHolder

    收起
    自动换行
    深色代码主题
    复制
    1. XComponent({
    2. type: XComponentType.SURFACE,
    3. })
    4. .id('XComponentSurfaceHolder')
    5. .onAttach(() => {
    6. this.xcNode = this.getUIContext().getAttachedFrameNodeById('XComponentSurfaceHolder');
    7. if (!this.xcNode) {
    8. return;
    9. }
    10. native.bindNode('XComponentSurfaceHolder', this.xcNode); // 跨语言调用至Native侧获取SurfaceHolder并绑定Surface生命周期回调
    11. this.currentStatus = 'index';
    12. })
    13. .onDetach(() => {
    14. native.unbindNode('XComponentSurfaceHolder');
    15. this.xcNode = null;
    16. })

绑定Surface生命周期

绑定Surface生命周期中的主要差异在于注册生命周期回调的接口不同,具体回调内执行的逻辑基本保持不变。

  • OH_NativeXComponent

    收起
    自动换行
    深色代码主题
    复制
    1. void PluginManager::Export(napi_env env, napi_value exports)
    2. {
    3. if ((env == nullptr) || (exports == nullptr)) {
    4. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "Export: env or exports is null");
    5. return;
    6. }
    7. napi_value exportInstance = nullptr;
    8. // 利用OH_NATIVE_XCOMPONENT_OBJ字段获取NativeXComponent实例
    9. if (napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance) != napi_ok) {
    10. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "Export: napi_get_named_property fail");
    11. return;
    12. }
    13. OH_NativeXComponent *nativeXComponent = nullptr;
    14. if (napi_unwrap(env, exportInstance, reinterpret_cast<void **>(&nativeXComponent)) != napi_ok) {
    15. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "Export: napi_unwrap fail");
    16. return;
    17. }
    18. char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'};
    19. uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1;
    20. // 从NativeXComponent实例中获取id属性用来和ArkTS侧的XComponent组件一一对应
    21. if (OH_NativeXComponent_GetXComponentId(nativeXComponent, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
    22. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager",
    23. "Export: OH_NativeXComponent_GetXComponentId fail");
    24. return;
    25. }
    26. std::string id(idStr);
    27. auto context = PluginManager::GetInstance();
    28. if ((context != nullptr) && (nativeXComponent != nullptr)) {
    29. context->SetNativeXComponent(id, nativeXComponent);
    30. auto render = context->GetRender(id);
    31. if (render != nullptr) {
    32. // 注册Surface生命周期
    33. render->RegisterCallback(nativeXComponent);
    34. render->Export(env, exports);
    35. }
    36. }
    37. }

    注册Surface生命周期。

    收起
    自动换行
    深色代码主题
    复制
    1. void PluginRender::RegisterCallback(OH_NativeXComponent* nativeXComponent)
    2. {
    3. // 注册XComponent的各种回调函数,包括Surface回调和各类事件回调
    4. renderCallback_.OnSurfaceCreated = OnSurfaceCreatedCB;
    5. renderCallback_.OnSurfaceChanged = OnSurfaceChangedCB;
    6. renderCallback_.OnSurfaceDestroyed = OnSurfaceDestroyedCB;
    7. // ...
    8. OH_NativeXComponent_RegisterCallback(nativeXComponent, &renderCallback_);
    9. // ...
    10. }
  • OH_ArkUI_SurfaceHolder

    收起
    自动换行
    深色代码主题
    复制
    1. napi_value PluginManager::BindNode(napi_env env, napi_callback_info info)
    2. {
    3. size_t argc = 2;
    4. napi_value args[2] = {nullptr};
    5. napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    6. std::string nodeId = value2String(env, args[0]);
    7. ArkUI_NodeHandle handle;
    8. OH_ArkUI_GetNodeHandleFromNapiValue(env, args[1], &handle); // 获取nodeHandle
    9. OH_ArkUI_SurfaceHolder *holder = OH_ArkUI_SurfaceHolder_Create(handle); // 获取SurfaceHolder
    10. nodeHandleMap_[nodeId] = handle;
    11. surfaceHolderMap_[handle] = holder;
    12. auto callback = OH_ArkUI_SurfaceCallback_Create(); // 创建SurfaceCallback
    13. callbackMap_[holder] = callback;
    14. auto render = new EGLRender();
    15. OH_ArkUI_SurfaceHolder_SetUserData(holder, render); // 将render保存在holder中
    16. OH_ArkUI_SurfaceCallback_SetSurfaceCreatedEvent(callback, OnSurfaceCreatedNative); // 注册OnSurfaceCreated回调
    17. OH_ArkUI_SurfaceCallback_SetSurfaceChangedEvent(callback, OnSurfaceChangedNative); // 注册OnSurfaceChanged回调
    18. OH_ArkUI_SurfaceCallback_SetSurfaceDestroyedEvent(callback, OnSurfaceDestroyedNative); // 注册OnSurfaceDestroyed回调
    19. OH_ArkUI_SurfaceHolder_AddSurfaceCallback(holder, callback); // 注册SurfaceCallback回调
    20. // ...
    21. return nullptr;
    22. }

获取NativeWindow方式

获取NativeWindow方式的差异如下:

  • OH_NativeXComponent

    在OnSurfaceCreated等生命周期回调返回的参数(即下面的void *window)中获取。

    收起
    自动换行
    深色代码主题
    复制
    1. void OnSurfaceCreatedCB(OH_NativeXComponent *component, void *window)
    2. {
    3. // ...
    4. }
    5. void OnSurfaceChangedCB(OH_NativeXComponent *component, void *window)
    6. {
    7. // ...
    8. }
    9. void OnSurfaceDestroyedCB(OH_NativeXComponent *component, void *window)
    10. {
    11. // ...
    12. }
    13. void DispatchTouchEventCB(OH_NativeXComponent *component, void *window)
    14. {
    15. // ...
    16. }
  • OH_ArkUI_SurfaceHolder

    调用OH_ArkUI_XComponent_GetNativeWindow接口从OH_ArkUI_SurfaceHolder中获取。

    收起
    自动换行
    深色代码主题
    复制
    1. void OnSurfaceCreatedNative(OH_ArkUI_SurfaceHolder *holder)
    2. {
    3. auto window = OH_ArkUI_XComponent_GetNativeWindow(holder); // 获取native window
    4. // ...
    5. }

监听交互事件

使用OH_NativeXComponent方式进行交互事件的监听,只能使用OH_NativeXComponent上相关的接口监听触摸、鼠标、按键等基础事件。而使用ArkUI NDK接口(通过ArkUI_NodeHandle),除监听基础事件外还能监听长按、拖拽等高级手势。

  • OH_NativeXComponent

    收起
    自动换行
    深色代码主题
    复制
    1. renderCallback_.DispatchTouchEvent = DispatchTouchEventCB; // 注册触摸事件
    2. OH_NativeXComponent_RegisterCallback(nativeXComponent, &renderCallback_);
    3. mouseCallback_.DispatchMouseEvent = DispatchMouseEventCB;
    4. mouseCallback_.DispatchHoverEvent = DispatchHoverEventCB;
    5. OH_NativeXComponent_RegisterMouseEventCallback(nativeXComponent, &mouseCallback_); // 注册鼠标事件
    6. OH_NativeXComponent_RegisterFocusEventCallback(nativeXComponent, OnFocusEventCB); // 注册获焦事件
    7. OH_NativeXComponent_RegisterKeyEventCallback(nativeXComponent, OnKeyEventCB); // 注册按键事件
    8. OH_NativeXComponent_RegisterBlurEventCallback(nativeXComponent, OnBlurEventCB); // 注册失焦事件
  • OH_ArkUI_SurfaceHolder

    以下只以注册触摸事件为例,鼠标、按键等更多事件请参考绑定基础输入事件

    收起
    自动换行
    深色代码主题
    复制
    1. if (nodeAPI->addNodeEventReceiver(handle, onEvent)) { // 添加事件监听,返回0表示成功,非0表示失败
    2. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "addNodeEventReceiver error");
    3. }
    4. if (nodeAPI->registerNodeEvent(handle, NODE_TOUCH_EVENT, 0, nullptr)) { // 用C接口注册touch事件,返回0表示成功,非0表示失败
    5. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "registerTouchEvent error");
    6. }

开发示例

在Native侧使用NativeWindow进行渲染绘制

以下展示完整使用NativeWindow和EGL接口进行自绘制的示例,主要开发场景如下:

  • 在ArkTS侧创建的XComponent组件可以将其对应的FrameNode节点传递到Native侧以获取ArkUI_NodeHandle,或者在Native侧直接创建XComponent组件对应的ArkUI_NodeHandle,然后调用OH_ArkUI_SurfaceHolder_Create接口创建OH_ArkUI_SurfaceHolder实例。
  • 基于OH_ArkUI_SurfaceHolder实例注册相应的生命周期回调,获取NativeWindow实例。
  • 利用NativeWindow和EGL接口开发自定义绘制内容,并申请提交Buffer到图形队列。
  • XComponent组件相关的无障碍、可变帧率等能力根据ArkUI_NodeHandle通过相关接口来实现。
  • XComponent组件上的基础事件(如点击、触摸)和手势事件(如滑动、缩放)可通过ArkUI_NodeHandle对象使用ArkUI NDK接口来监听,具体可参考添加事件监听

接口说明

展开
接口名 描述
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节点在获得焦点时是否需要显示软键盘。

开发步骤

以下步骤通过在ArkTS侧创建SURFACE类型的XComponent为例(Native侧如何创建XComponent组件对应的ArkUI_NodeHandle可参考ArkUI_NativeNodeAPI_1),描述了如何使用XComponent组件调用OH_ArkUI_SurfaceHolder相关接口管理Surface生命周期,并在Native侧创建EGL/GLES环境,实现在主页面绘制图形,以及可以改变图形的颜色。

  1. 在界面中定义XComponent。

    收起
    自动换行
    深色代码主题
    复制
    1. import native from 'libnativerender.so';
    2. import { common } from '@kit.AbilityKit';
    3. // ...
    4. @Component
    5. export struct PageThree {
    6. @State isShow: boolean = true;
    7. @State minRate: number = 0;
    8. @State maxRate: number = 120;
    9. @State expected: number = 60;
    10. needSoftKeyboard: boolean = false;
    11. @State needSoftKeyboardState: string = 'needSoftKeyboard=' + this.needSoftKeyboard;
    12. private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
    13. // 请将$r('app.string.pagethree_text1')替换为实际资源文件,在本示例中该资源文件的value值为"单指点击XComponent软键盘消失"
    14. @State text: ResourceStr = $r('app.string.pagethree_text1');
    15. controller: TextInputController = new TextInputController();
    16. myNodeController: MyNodeController = new MyNodeController();
    17. build() {
    18. NavDestination() {
    19. Column() {
    20. TextInput({ text: this.text, placeholder: 'please input ...', controller: this.controller })
    21. .id('textInput')
    22. .placeholderColor(Color.Grey)
    23. .placeholderFont({ size: 14, weight: 400 })
    24. .caretColor(Color.Blue)
    25. .width(400)
    26. .height(40)
    27. .margin(10)
    28. .fontSize(14)
    29. .fontColor(Color.Black)
    30. .onChange((value: string) => {
    31. this.text = value
    32. })
    33. Column() {
    34. if (this.isShow) {
    35. NodeContainer(this.myNodeController)
    36. .width(200)
    37. .height(200)
    38. .focusable(true)
    39. .focusOnTouch(true)
    40. .defaultFocus(true)
    41. }
    42. }.height(200)
    43. // 请将$r('app.string.pagethree_text2')替换为实际资源文件,在本示例中该资源文件的value值为"创建/销毁"
    44. Button($r('app.string.pagethree_text2')).onClick(() => {
    45. this.isShow = !this.isShow;
    46. }).width('50%')
    47. .margin({
    48. top: 10,
    49. bottom: 10,
    50. left: 12,
    51. right: 12
    52. })
    53. Column() {
    54. // 请将$r('app.string.pagethree_text3')替换为实际资源文件,在本示例中该资源文件的value值为"期望帧率设置:"
    55. Text($r('app.string.pagethree_text3'))
    56. .textAlign(TextAlign.Start)
    57. .fontSize(15)
    58. .border({ width: 1 })
    59. .padding(10)
    60. .width('100%')
    61. .margin(5)
    62. Text('min: ' + this.minRate)
    63. Slider({
    64. value: this.minRate,
    65. min: 0,
    66. max: 240,
    67. step: 1
    68. }).onChange((value: number, mode: SliderChangeMode) => {
    69. this.minRate = value;
    70. native.setFrameRate(this.myNodeController.xComponentId, this.minRate, this.maxRate, this.expected)
    71. }).width('100%')
    72. .id('minSlider')
    73. Text('max: ' + this.maxRate)
    74. Slider({
    75. value: this.maxRate,
    76. min: 0,
    77. max: 240,
    78. step: 1
    79. }).onChange((value: number, mode: SliderChangeMode) => {
    80. this.maxRate = value;
    81. native.setFrameRate(this.myNodeController.xComponentId, this.minRate, this.maxRate, this.expected)
    82. }).width('100%')
    83. .id('maxSlider')
    84. Text('expected: ' + this.expected)
    85. Slider({
    86. value: this.expected,
    87. min: 0,
    88. max: 240,
    89. step: 1
    90. }).onChange((value: number, mode: SliderChangeMode) => {
    91. this.expected = value;
    92. native.setFrameRate(this.myNodeController.xComponentId, this.minRate, this.maxRate, this.expected)
    93. }).width('100%')
    94. .id('expectedSlider')
    95. }.backgroundColor('#F0FAFF')
    96. Button(this.needSoftKeyboardState)
    97. .onClick(() => {
    98. this.needSoftKeyboard = !this.needSoftKeyboard;
    99. this.needSoftKeyboardState = 'needSoftKeyboard=' + this.needSoftKeyboard;
    100. native.setNeedSoftKeyboard(this.myNodeController.xComponentId, this.needSoftKeyboard);
    101. // 请将$r('app.string.pagethree_text4')替换为实际资源文件,在本示例中该资源文件的value值为"单指点击XComponent软键盘不消失"
    102. // 请将$r('app.string.pagethree_text1')替换为实际资源文件,在本示例中该资源文件的value值为"单指点击XComponent软键盘消失"
    103. this.text = this.needSoftKeyboard ? $r('app.string.pagethree_text4') : $r('app.string.pagethree_text1')
    104. })
    105. .width('50%')
    106. .margin({
    107. top: 10,
    108. bottom: 10,
    109. left: 12,
    110. right: 12
    111. })
    112. // ...
    113. }
    114. .width('100%')
    115. }
    116. }
    117. }
  2. Node-API模块注册,具体使用请参考Node-API开发规范

    收起
    自动换行
    深色代码主题
    复制
    1. #include <hilog/log.h>
    2. #include "common/common.h"
    3. #include "manager/plugin_manager.h"
    4. namespace NativeXComponentSample {
    5. // 在napi_init.cpp文件中,Init方法注册接口函数,从而将封装的C++方法传递出来,供ArkTS侧调用
    6. EXTERN_C_START
    7. static napi_value Init(napi_env env, napi_value exports)
    8. {
    9. OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Init", "Init begins");
    10. if ((env == nullptr) || (exports == nullptr)) {
    11. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init", "env or exports is null");
    12. return nullptr;
    13. }
    14. // 向ArkTS侧暴露接口
    15. napi_property_descriptor desc[] = {
    16. // ···
    17. {"bindNode", nullptr, PluginManager::BindNode, nullptr, nullptr, nullptr, napi_default, nullptr},
    18. {"unbindNode", nullptr, PluginManager::UnbindNode, nullptr, nullptr, nullptr, napi_default, nullptr},
    19. {"setFrameRate", nullptr, PluginManager::SetFrameRate, nullptr, nullptr, nullptr, napi_default, nullptr},
    20. {"setNeedSoftKeyboard", nullptr, PluginManager::SetNeedSoftKeyboard, nullptr, nullptr, nullptr, napi_default,
    21. nullptr},
    22. // ···
    23. };
    24. if (napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc) != napi_ok) {
    25. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init", "napi_define_properties failed");
    26. return nullptr;
    27. }
    28. PluginManager::GetInstance()->Export(env, exports);
    29. return exports;
    30. }
    31. EXTERN_C_END
    32. // 编写接口的描述信息,根据实际需要可以修改对应参数
    33. static napi_module nativerenderModule = { .nm_version = 1,
    34. .nm_flags = 0,
    35. .nm_filename = nullptr,
    36. // 入口函数
    37. .nm_register_func = Init, // 指定加载对应模块时的回调函数
    38. // 模块名称
    39. .nm_modname = "nativerender", // 指定模块名称,对于XComponent相关开发,这个名称必须和ArkTS侧XComponent中libraryname的值保持一致
    40. .nm_priv = ((void*)0),
    41. .reserved = { 0 } };
    42. // __attribute__((constructor))修饰的方法由系统自动调用,使用Node-API接口napi_module_register()传入模块描述信息进行模块注册
    43. extern "C" __attribute__((constructor)) void RegisterModule(void)
    44. {
    45. napi_module_register(&nativerenderModule);
    46. }
    47. } // namespace NativeXComponentSample
  3. 注册XComponent生命周期、事件、无障碍和可变帧率回调,使用CAPI实现往XComponent注册回调函数。

    (1) 定义BindNode、UnbindNode、SetFrameRate、SetNeedSoftKeyboard方法,暴露到ArkTS侧的bindNode、unbindNode、setFrameRate、setNeedSoftKeyboard方法会执行该方法。

    收起
    自动换行
    深色代码主题
    复制
    1. // plugin_manager.h
    2. namespace NativeXComponentSample {
    3. // ···
    4. class PluginManager {
    5. public:
    6. // ···
    7. static napi_value BindNode(napi_env env, napi_callback_info info);
    8. static napi_value UnbindNode(napi_env env, napi_callback_info info);
    9. static napi_value SetFrameRate(napi_env env, napi_callback_info info);
    10. static napi_value SetNeedSoftKeyboard(napi_env env, napi_callback_info info);
    11. // ···
    12. public:
    13. // ···
    14. static std::unordered_map<std::string, ArkUI_NodeHandle> nodeHandleMap_;
    15. static std::unordered_map<void *, OH_ArkUI_SurfaceCallback *> callbackMap_;
    16. static std::unordered_map<void *, OH_ArkUI_SurfaceHolder *> surfaceHolderMap_;
    17. static ArkUI_AccessibilityProvider *provider_;
    18. };
    19. } // namespace NativeXComponentSample
    收起
    自动换行
    深色代码主题
    复制
    1. // plugin_manager.cpp
    2. std::unordered_map<std::string, ArkUI_NodeHandle> PluginManager::nodeHandleMap_;
    3. std::unordered_map<void *, OH_ArkUI_SurfaceCallback *> PluginManager::callbackMap_;
    4. std::unordered_map<void *, OH_ArkUI_SurfaceHolder *> PluginManager::surfaceHolderMap_;
    5. ArkUI_AccessibilityProvider *PluginManager::provider_ = nullptr;
    6. ArkUI_NativeNodeAPI_1 *nodeAPI = reinterpret_cast<ArkUI_NativeNodeAPI_1 *>(
    7. OH_ArkUI_QueryModuleInterfaceByName(ARKUI_NATIVE_NODE, "ArkUI_NativeNodeAPI_1"));
    8. // ...
    9. static std::string value2String(napi_env env, napi_value value)
    10. {
    11. size_t stringSize = 0;
    12. napi_get_value_string_utf8(env, value, nullptr, 0, &stringSize);
    13. std::string valueString;
    14. valueString.resize(stringSize);
    15. napi_get_value_string_utf8(env, value, &valueString[0], stringSize+1, &stringSize);
    16. return valueString;
    17. }
    18. // ...
    19. napi_value PluginManager::BindNode(napi_env env, napi_callback_info info)
    20. {
    21. size_t argc = 2;
    22. napi_value args[2] = {nullptr};
    23. napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    24. std::string nodeId = value2String(env, args[0]);
    25. ArkUI_NodeHandle handle;
    26. OH_ArkUI_GetNodeHandleFromNapiValue(env, args[1], &handle); // 获取 nodeHandle
    27. OH_ArkUI_SurfaceHolder *holder = OH_ArkUI_SurfaceHolder_Create(handle); // 获取 SurfaceHolder
    28. nodeHandleMap_[nodeId] = handle;
    29. surfaceHolderMap_[handle] = holder;
    30. auto callback = OH_ArkUI_SurfaceCallback_Create(); // 创建 SurfaceCallback
    31. callbackMap_[holder] = callback;
    32. auto render = new EGLRender();
    33. OH_ArkUI_SurfaceHolder_SetUserData(holder, render); // 将render保存在holder中
    34. OH_ArkUI_SurfaceCallback_SetSurfaceCreatedEvent(callback, OnSurfaceCreatedNative); // 注册OnSurfaceCreated回调
    35. OH_ArkUI_SurfaceCallback_SetSurfaceChangedEvent(callback, OnSurfaceChangedNative); // 注册OnSurfaceChanged回调
    36. OH_ArkUI_SurfaceCallback_SetSurfaceDestroyedEvent(callback, OnSurfaceDestroyedNative); // 注册OnSurfaceDestroyed回调
    37. OH_ArkUI_SurfaceCallback_SetSurfaceShowEvent(callback, OnSurfaceShowNative); // 注册OnSurfaceShow回调
    38. OH_ArkUI_SurfaceCallback_SetSurfaceHideEvent(callback, OnSurfaceHideNative); // 注册OnSurfaceHide回调
    39. OH_ArkUI_XComponent_RegisterOnFrameCallback(handle, OnFrameCallbackNative); // 注册OnFrameCallback回调
    40. OH_ArkUI_SurfaceHolder_AddSurfaceCallback(holder, callback); // 注册SurfaceCallback回调
    41. if (nodeAPI->addNodeEventReceiver(handle, onEvent)) { // 添加事件监听,返回0表示成功,非0表示失败
    42. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "addNodeEventReceiver error");
    43. }
    44. if (nodeAPI->registerNodeEvent(handle, NODE_TOUCH_EVENT, 0, nullptr)) { // 用C接口注册touch事件,返回0表示成功,非0表示失败
    45. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "registerTouchEvent error");
    46. }
    47. provider_ = OH_ArkUI_AccessibilityProvider_Create(handle); // 创建一个ArkUI_AccessibilityProvider类型的对象
    48. /**
    49. * 获取ArkUI_AccessibilityProvider后,如果注册无障碍回调函数请参考:
    50. * https://gitcode.com/openharmony/docs/blob/master/zh-cn/application-dev/ui/ndk-accessibility-xcomponent.md
    51. * **/
    52. return nullptr;
    53. }
    54. napi_value PluginManager::UnbindNode(napi_env env, napi_callback_info info)
    55. {
    56. size_t argc = 1;
    57. napi_value args[1] = {nullptr};
    58. napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    59. std::string nodeId = value2String(env, args[0]);
    60. ArkUI_NodeHandle node;
    61. if (nodeHandleMap_.find(nodeId) == nodeHandleMap_.end()) {
    62. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "UnbindNode", "nodeId not exit error");
    63. return nullptr;
    64. }
    65. node = nodeHandleMap_[nodeId];
    66. OH_ArkUI_XComponent_UnregisterOnFrameCallback(node); // 解注册帧回调
    67. OH_ArkUI_AccessibilityProvider_Dispose(provider_); // 销毁 ArkUI_AccessibilityProvider
    68. auto holder = surfaceHolderMap_[node];
    69. if (PluginManager::callbackMap_.count(holder)) {
    70. auto callback = PluginManager::callbackMap_[holder];
    71. OH_ArkUI_SurfaceHolder_RemoveSurfaceCallback(holder, callback); // 移除SurfaceCallback
    72. OH_ArkUI_SurfaceCallback_Dispose(callback); // 销毁surfaceCallback
    73. PluginManager::callbackMap_.erase(holder);
    74. }
    75. auto render = reinterpret_cast<EGLRender*>(OH_ArkUI_SurfaceHolder_GetUserData(holder));
    76. delete render; // 销毁EGLRender对象
    77. OH_ArkUI_SurfaceHolder_Dispose(holder); // 销毁surfaceHolder
    78. nodeAPI->disposeNode(node); // 销毁nodeHandle
    79. nodeHandleMap_.erase(nodeId);
    80. return nullptr;
    81. }
    82. napi_value PluginManager::SetFrameRate(napi_env env, napi_callback_info info)
    83. {
    84. size_t argc = 4;
    85. napi_value args[4] = {nullptr};
    86. napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    87. std::string nodeId = value2String(env, args[0]);
    88. auto node = nodeHandleMap_[nodeId];
    89. int32_t min = 0;
    90. napi_get_value_int32(env, args[FIRST_ARG], &min);
    91. int32_t max = 0;
    92. napi_get_value_int32(env, args[SECOND_ARG], &max);
    93. int32_t expected = 0;
    94. napi_get_value_int32(env, args[THIRD_ARG], &expected);
    95. OH_NativeXComponent_ExpectedRateRange range = {.min = min, .max = max, .expected = expected};
    96. OH_ArkUI_XComponent_SetExpectedFrameRateRange(node, range); // 设置期望帧率
    97. return nullptr;
    98. }
    99. napi_value PluginManager::SetNeedSoftKeyboard(napi_env env, napi_callback_info info)
    100. {
    101. size_t argc = 2;
    102. napi_value args[2] = {nullptr};
    103. napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    104. std::string nodeId = value2String(env, args[0]);
    105. ArkUI_NodeHandle node;
    106. if (nodeHandleMap_.find(nodeId) == nodeHandleMap_.end()) {
    107. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "SetNeedSoftKeyboard", "nodeId not exit error");
    108. return nullptr;
    109. }
    110. node = nodeHandleMap_[nodeId];
    111. bool needSoftKeyboard = false;
    112. napi_get_value_bool(env, args[1], &needSoftKeyboard);
    113. OH_ArkUI_XComponent_SetNeedSoftKeyboard(node, needSoftKeyboard); // 设置是否需要软键盘
    114. return nullptr;
    115. }

    (2) 定义Surface创建成功,发生改变,销毁和事件,可变帧率回调接口。

    收起
    自动换行
    深色代码主题
    复制
    1. void OnSurfaceCreatedNative(OH_ArkUI_SurfaceHolder *holder)
    2. {
    3. auto window = OH_ArkUI_XComponent_GetNativeWindow(holder); // 获取native window
    4. auto render = reinterpret_cast<EGLRender*>(OH_ArkUI_SurfaceHolder_GetUserData(holder));
    5. render->SetUpEGLContext(window); // 初始化egl环境
    6. }
    7. void OnSurfaceChangedNative(OH_ArkUI_SurfaceHolder *holder, uint64_t width, uint64_t height)
    8. {
    9. EGLRender* render = reinterpret_cast<EGLRender*>(OH_ArkUI_SurfaceHolder_GetUserData(holder));
    10. render->SetEGLWindowSize(width, height); // 设置绘制区域大小
    11. render->DrawStar(true); // 绘制五角星
    12. }
    13. void OnSurfaceDestroyedNative(OH_ArkUI_SurfaceHolder *holder)
    14. {
    15. OH_LOG_Print(LOG_APP, LOG_ERROR, 0xff00, "onBind", "on destroyed");
    16. EGLRender* render = reinterpret_cast<EGLRender*>(OH_ArkUI_SurfaceHolder_GetUserData(holder));
    17. render->DestroySurface(); // 销毁eglSurface相关资源
    18. }
    19. void OnSurfaceShowNative(OH_ArkUI_SurfaceHolder *holder)
    20. {
    21. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "on surface show");
    22. }
    23. void OnSurfaceHideNative(OH_ArkUI_SurfaceHolder *holder)
    24. {
    25. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "on surface hide");
    26. }
    27. void OnFrameCallbackNative(ArkUI_NodeHandle node, uint64_t timestamp, uint64_t targetTimestamp)
    28. {
    29. if (!PluginManager::surfaceHolderMap_.count(node)) {
    30. return;
    31. }
    32. static uint64_t count = 0;
    33. count++;
    34. // 在头文件plugin_manager.h中定义,FRAME_COUNT的值为50
    35. if (count % FRAME_COUNT == 0) {
    36. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "OnFrameCallback count = %{public}ld", count);
    37. }
    38. }
    39. void onEvent(ArkUI_NodeEvent *event)
    40. {
    41. auto eventType = OH_ArkUI_NodeEvent_GetEventType(event); // 获取组件事件类型
    42. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "on event");
    43. if (eventType == NODE_TOUCH_EVENT) {
    44. ArkUI_NodeHandle handle = OH_ArkUI_NodeEvent_GetNodeHandle(event); // 获取触发该事件的组件对象
    45. auto holder = PluginManager::surfaceHolderMap_[handle];
    46. EGLRender* render = reinterpret_cast<EGLRender*>(OH_ArkUI_SurfaceHolder_GetUserData(holder));
    47. render->DrawStar(false); // 绘制五角星
    48. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "on touch");
    49. }
    50. }
  4. 初始化环境,包括初始化可用的EGLDisplay、确定可用的Surface配置、创建渲染区域Surface、创建并关联上下文等。

    收起
    自动换行
    深色代码主题
    复制
    1. // EGLConst.h
    2. #include <EGL/egl.h>
    3. #include <EGL/eglext.h>
    4. #include <GLES3/gl3.h>
    5. const unsigned int LOG_PRINT_DOMAIN = 0xFF00;
    6. /**
    7. * Program 错误
    8. */
    9. const GLuint PROGRAM_ERROR = 0;
    10. /**
    11. * 位置错误。
    12. */
    13. const GLint POSITION_ERROR = -1;
    14. /**
    15. * 默认x坐标。
    16. */
    17. const int DEFAULT_X_POSITION = 0;
    18. /**
    19. * 默认y坐标。
    20. */
    21. const int DEFAULT_Y_POSITION = 0;
    22. /**
    23. * Gl 红色默认值。
    24. */
    25. const GLfloat GL_RED_DEFAULT = 0.0;
    26. /**
    27. * Gl 绿色默认值。
    28. */
    29. const GLfloat GL_GREEN_DEFAULT = 0.0;
    30. /**
    31. * Gl 蓝色默认值。
    32. */
    33. const GLfloat GL_BLUE_DEFAULT = 0.0;
    34. /**
    35. * Gl 透明度。
    36. */
    37. const GLfloat GL_ALPHA_DEFAULT = 1.0;
    38. /**
    39. * Pointer 数量。
    40. */
    41. const GLint POINTER_SIZE = 2;
    42. /**
    43. * Triangle fan 尺寸。
    44. */
    45. const GLsizei TRIANGLE_FAN_SIZE = 4;
    46. /**
    47. * 50%。
    48. */
    49. const float FIFTY_PERCENT = 0.5;
    50. /**
    51. * 位置句柄名字。
    52. */
    53. const char POSITION_NAME[] = "a_position";
    54. // ···
    55. /**
    56. * 背景色 #f4f4f4.
    57. */
    58. const GLfloat BACKGROUND_COLOR[] = {244.0f / 255, 244.0f / 255, 244.0f / 255, 1.0f};
    59. // ···
    60. /**
    61. * Draw 颜色 #7E8FFB.
    62. */
    63. const GLfloat DRAW_COLOR[] = {126.0f / 255, 143.0f / 255, 251.0f / 255, 1.0f};
    64. /**
    65. * Change 颜色 #92D6CC.
    66. */
    67. const GLfloat CHANGE_COLOR[] = {146.0f / 255, 214.0f / 255, 204.0f / 255, 1.0f};
    68. /**
    69. * 背景区域。
    70. */
    71. const GLfloat BACKGROUND_RECTANGLE_VERTICES[] = {-1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f};
    72. const EGLint ATTRIB_LIST[] = {
    73. // 键,值。
    74. EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8,
    75. EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
    76. // 结束。
    77. EGL_NONE};
    78. const EGLint CONTEXT_ATTRIBS[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
    79. /**
    80. * 顶点着色器
    81. */
    82. const char VERTEX_SHADER[] = "#version 300 es\n"
    83. "layout(location = 0) in vec4 a_position;\n"
    84. "layout(location = 1) in vec4 a_color; \n"
    85. "out vec4 v_color; \n"
    86. "void main() \n"
    87. "{ \n"
    88. " gl_Position = a_position; \n"
    89. " v_color = a_color; \n"
    90. "} \n";
    91. /**
    92. * 片元着色器。
    93. */
    94. const char FRAGMENT_SHADER[] = "#version 300 es\n"
    95. "precision mediump float; \n"
    96. "in vec4 v_color; \n"
    97. "out vec4 fragColor; \n"
    98. "void main() \n"
    99. "{ \n"
    100. " fragColor = v_color; \n"
    101. "} \n";
    收起
    自动换行
    深色代码主题
    复制
    1. // EGLRender.h
    2. #include "EGLConst.h"
    3. #include <EGL/egl.h>
    4. #include <EGL/eglext.h>
    5. #include <EGL/eglplatform.h>
    6. #include <GLES3/gl3.h>
    7. #include <string>
    8. class EGLRender {
    9. public:
    10. bool SetUpEGLContext(void *window);
    11. void SetEGLWindowSize(int width, int height);
    12. void DrawStar(bool drawColor);
    13. void DestroySurface();
    14. // ···
    15. std::string xcomponentId;
    16. EGLNativeWindowType eglWindow_;
    17. EGLDisplay eglDisplay_ = EGL_NO_DISPLAY;
    18. EGLConfig eglConfig_ = EGL_NO_CONFIG_KHR;
    19. EGLSurface eglSurface_ = EGL_NO_SURFACE;
    20. EGLContext eglContext_ = EGL_NO_CONTEXT;
    21. GLuint program_;
    22. int width_ = 0;
    23. int height_ = 0;
    24. private:
    25. GLint PrepareDraw();
    26. bool ExecuteDraw(GLint position, const GLfloat *color, const GLfloat shapeVertices[]);
    27. };
    收起
    自动换行
    深色代码主题
    复制
    1. // EGLRender.cpp
    2. #include "EGLRender.h"
    3. #include "EGLConst.h"
    4. #include <EGL/egl.h>
    5. #include <EGL/eglext.h>
    6. #include <GLES3/gl3.h>
    7. #include <cmath>
    8. #include <cstdio>
    9. #include <algorithm>
    10. #include <hilog/log.h>
    11. #include <iostream>
    12. namespace {
    13. void Rotate2d(GLfloat centerX, GLfloat centerY, GLfloat *rotateX, GLfloat *rotateY, GLfloat theta)
    14. {
    15. GLfloat tempX = cos(theta) * (*rotateX - centerX) - sin(theta) * (*rotateY - centerY);
    16. GLfloat tempY = sin(theta) * (*rotateX - centerX) + cos(theta) * (*rotateY - centerY);
    17. *rotateX = tempX + centerX;
    18. *rotateY = tempY + centerY;
    19. }
    20. GLuint LoadShader(GLenum type, const char *shaderSrc)
    21. {
    22. if ((type <= 0) || (shaderSrc == nullptr)) {
    23. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "glCreateShader type or shaderSrc error");
    24. return PROGRAM_ERROR;
    25. }
    26. GLuint shader = glCreateShader(type);
    27. if (shader == 0) {
    28. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "glCreateShader unable to load shader");
    29. return PROGRAM_ERROR;
    30. }
    31. // The gl function has no return value.
    32. glShaderSource(shader, 1, &shaderSrc, nullptr);
    33. glCompileShader(shader);
    34. GLint compiled;
    35. glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
    36. if (compiled != 0) {
    37. return shader;
    38. }
    39. GLint infoLen = 0;
    40. glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
    41. if (infoLen <= 1) {
    42. glDeleteShader(shader);
    43. return PROGRAM_ERROR;
    44. }
    45. char *infoLog = (char *)malloc(sizeof(char) * (infoLen + 1));
    46. if (infoLog != nullptr) {
    47. memset(infoLog, 0, infoLen + 1);
    48. glGetShaderInfoLog(shader, infoLen, nullptr, infoLog);
    49. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "glCompileShader error = %s", infoLog);
    50. free(infoLog);
    51. infoLog = nullptr;
    52. }
    53. glDeleteShader(shader);
    54. return PROGRAM_ERROR;
    55. }
    56. // 创建program
    57. GLuint CreateProgram(const char *vertexShader, const char *fragShader)
    58. {
    59. if ((vertexShader == nullptr) || (fragShader == nullptr)) {
    60. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender",
    61. "createProgram: vertexShader or fragShader is null");
    62. return PROGRAM_ERROR;
    63. }
    64. GLuint vertex = LoadShader(GL_VERTEX_SHADER, vertexShader);
    65. if (vertex == PROGRAM_ERROR) {
    66. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "createProgram vertex error");
    67. return PROGRAM_ERROR;
    68. }
    69. GLuint fragment = LoadShader(GL_FRAGMENT_SHADER, fragShader);
    70. if (fragment == PROGRAM_ERROR) {
    71. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "createProgram fragment error");
    72. return PROGRAM_ERROR;
    73. }
    74. GLuint program = glCreateProgram();
    75. if (program == PROGRAM_ERROR) {
    76. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "createProgram program error");
    77. glDeleteShader(vertex);
    78. glDeleteShader(fragment);
    79. return PROGRAM_ERROR;
    80. }
    81. // 该gl函数没有返回值。
    82. glAttachShader(program, vertex);
    83. glAttachShader(program, fragment);
    84. glLinkProgram(program);
    85. GLint linked;
    86. glGetProgramiv(program, GL_LINK_STATUS, &linked);
    87. if (linked != 0) {
    88. glDeleteShader(vertex);
    89. glDeleteShader(fragment);
    90. return program;
    91. }
    92. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "createProgram linked error");
    93. GLint infoLen = 0;
    94. glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen);
    95. if (infoLen > 1) {
    96. char *infoLog = (char *)malloc(sizeof(char) * (infoLen + 1));
    97. memset(infoLog, 0, infoLen + 1);
    98. glGetProgramInfoLog(program, infoLen, nullptr, infoLog);
    99. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "glLinkProgram error = %s", infoLog);
    100. free(infoLog);
    101. infoLog = nullptr;
    102. }
    103. glDeleteShader(vertex);
    104. glDeleteShader(fragment);
    105. glDeleteProgram(program);
    106. return PROGRAM_ERROR;
    107. }
    108. } // namespace
    109. bool EGLRender::SetUpEGLContext(void *window)
    110. {
    111. OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "EGLRender", "EglContextInit execute");
    112. eglWindow_ = (EGLNativeWindowType)(window);
    113. // 初始化display。
    114. eglDisplay_ = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    115. if (eglDisplay_ == EGL_NO_DISPLAY) {
    116. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "eglGetDisplay: unable to get EGL display");
    117. return false;
    118. }
    119. EGLint majorVersion;
    120. EGLint minorVersion;
    121. if (!eglInitialize(eglDisplay_, &majorVersion, &minorVersion)) {
    122. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender",
    123. "eglInitialize: unable to get initialize EGL display");
    124. return false;
    125. };
    126. // 选择配置。
    127. const EGLint maxConfigSize = 1;
    128. EGLint numConfigs;
    129. if (!eglChooseConfig(eglDisplay_, ATTRIB_LIST, &eglConfig_, maxConfigSize, &numConfigs)) {
    130. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "eglChooseConfig: unable to choose configs");
    131. return false;
    132. };
    133. // 创建环境。
    134. // 创建 Surface。
    135. eglSurface_ = eglCreateWindowSurface(eglDisplay_, eglConfig_, eglWindow_, NULL);
    136. if (eglSurface_ == nullptr) {
    137. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender",
    138. "eglCreateWindowSurface: unable to create surface");
    139. return false;
    140. }
    141. // 创建上下文。
    142. eglContext_ = eglCreateContext(eglDisplay_, eglConfig_, EGL_NO_CONTEXT, CONTEXT_ATTRIBS);
    143. if (!eglMakeCurrent(eglDisplay_, eglSurface_, eglSurface_, eglContext_)) {
    144. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "eglMakeCurrent failed");
    145. return false;
    146. }
    147. // 创建program。
    148. program_ = CreateProgram(VERTEX_SHADER, FRAGMENT_SHADER);
    149. if (program_ == PROGRAM_ERROR) {
    150. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "CreateProgram: unable to create program");
    151. return false;
    152. }
    153. return true;
    154. }
    155. GLint EGLRender::PrepareDraw()
    156. {
    157. if ((eglDisplay_ == nullptr) || (eglSurface_ == nullptr) || (eglContext_ == nullptr) ||
    158. (!eglMakeCurrent(eglDisplay_, eglSurface_, eglSurface_, eglContext_))) {
    159. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "PrepareDraw: param error");
    160. return POSITION_ERROR;
    161. }
    162. // 该gl函数没有返回值。
    163. glViewport(DEFAULT_X_POSITION, DEFAULT_Y_POSITION, width_, height_);
    164. glClearColor(GL_RED_DEFAULT, GL_GREEN_DEFAULT, GL_BLUE_DEFAULT, GL_ALPHA_DEFAULT);
    165. glClear(GL_COLOR_BUFFER_BIT);
    166. glUseProgram(program_);
    167. return glGetAttribLocation(program_, POSITION_NAME);
    168. }
    169. // 绘制五角星
    170. void EGLRender::DrawStar(bool drawColor)
    171. {
    172. OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "EGLRender", "Draw");
    173. GLint position = PrepareDraw();
    174. if (position == POSITION_ERROR) {
    175. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "Draw get position failed");
    176. return;
    177. }
    178. // 绘制背景
    179. if (!ExecuteDraw(position, BACKGROUND_COLOR, BACKGROUND_RECTANGLE_VERTICES)) {
    180. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "Draw execute draw background failed");
    181. return;
    182. }
    183. // 将其划分为五个四边形,并计算其中一个四边形的顶点
    184. GLfloat rotateX = 0;
    185. GLfloat rotateY = FIFTY_PERCENT * height_;
    186. GLfloat centerX = 0;
    187. // 将角度 54° 和 18° 转换为弧度
    188. GLfloat centerY = -rotateY * (M_PI / 180 * 54) * (M_PI / 180 * 18);
    189. // 将角度 18° 转换为弧度
    190. GLfloat leftX = -rotateY * (M_PI / 180 * 18);
    191. GLfloat leftY = 0;
    192. // 将角度 18° 转换为弧度
    193. GLfloat rightX = rotateY * (M_PI / 180 * 18);
    194. GLfloat rightY = 0;
    195. // 确定绘制四边形的顶点,使用绘制区域的百分比表示
    196. const GLfloat shapeVertices[] = {centerX / width_, centerY / height_, leftX / width_, leftY / height_,
    197. rotateX / width_, rotateY / height_, rightX / width_, rightY / height_};
    198. auto color = drawColor ? DRAW_COLOR : CHANGE_COLOR;
    199. if (!ExecuteDraw(position, color, shapeVertices)) {
    200. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "Draw execute draw shape failed");
    201. return;
    202. }
    203. // 将角度 72° 转换为弧度
    204. GLfloat rad = M_PI / 180 * 72;
    205. // 旋转四次。
    206. // 在头文件EGLConst.h中定义,NUM_0的值为0,NUM_4的值为4
    207. for (int i = NUM_0; i < NUM_4; ++i) {
    208. // 旋转得其他四个四边形的顶点
    209. Rotate2d(centerX, centerY, &rotateX, &rotateY, rad);
    210. Rotate2d(centerX, centerY, &leftX, &leftY, rad);
    211. Rotate2d(centerX, centerY, &rightX, &rightY, rad);
    212. // 确定绘制四边形的顶点,使用绘制区域的百分比表示
    213. const GLfloat shapeVertices[] = {centerX / width_, centerY / height_, leftX / width_, leftY / height_,
    214. rotateX / width_, rotateY / height_, rightX / width_, rightY / height_};
    215. // 绘制图形
    216. if (!ExecuteDraw(position, color, shapeVertices)) {
    217. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "Draw execute draw shape failed");
    218. return;
    219. }
    220. }
    221. // 将绘制命令提交给GPU,GPU执行完成后将渲染结果显示到屏幕
    222. glFlush();
    223. glFinish();
    224. if (!eglSwapBuffers(eglDisplay_, eglSurface_)) {
    225. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "Draw FinishDraw failed");
    226. return;
    227. }
    228. }
    229. // ...
    230. bool EGLRender::ExecuteDraw(GLint position, const GLfloat *color, const GLfloat shapeVertices[])
    231. {
    232. if ((position < 0) || (color == nullptr)) {
    233. OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "ExecuteDraw: param error");
    234. return false;
    235. }
    236. // 该gl函数没有返回值。
    237. glVertexAttribPointer(position, POINTER_SIZE, GL_FLOAT, GL_FALSE, 0, shapeVertices);
    238. glEnableVertexAttribArray(position);
    239. glVertexAttrib4fv(1, color);
    240. glDrawArrays(GL_TRIANGLE_FAN, 0, TRIANGLE_FAN_SIZE);
    241. glDisableVertexAttribArray(position);
    242. return true;
    243. }
    244. void EGLRender::SetEGLWindowSize(int width, int height)
    245. {
    246. width_ = width;
    247. height_ = height;
    248. }
    249. // 释放相关资源
    250. void EGLRender::DestroySurface()
    251. {
    252. if ((eglDisplay_ == nullptr) || (eglSurface_ == nullptr) || (!eglDestroySurface(eglDisplay_, eglSurface_))) {
    253. OH_LOG_Print(LOG_APP, LOG_ERROR, 0xff00, "EGLRender", "Release eglDestroySurface failed");
    254. }
    255. if ((eglDisplay_ == nullptr) || (eglContext_ == nullptr) || (!eglDestroyContext(eglDisplay_, eglContext_))) {
    256. OH_LOG_Print(LOG_APP, LOG_ERROR, 0xff00, "EGLRender", "Release eglDestroyContext failed");
    257. }
    258. if ((eglDisplay_ == nullptr) || (!eglTerminate(eglDisplay_))) {
    259. OH_LOG_Print(LOG_APP, LOG_ERROR, 0xff00, "EGLRender", "Release eglTerminate failed");
    260. }
    261. eglDisplay_ = EGL_NO_DISPLAY;
    262. eglSurface_ = EGL_NO_SURFACE;
    263. eglContext_ = EGL_NO_CONTEXT;
    264. }
  5. CMakeLists,使用CMake工具链将C++源代码编译成动态链接库文件。

    收起
    自动换行
    深色代码主题
    复制
    1. # the minimum version of CMake.
    2. cmake_minimum_required(VERSION 3.4.1)
    3. project(XComponent)
    4. set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
    5. add_definitions(-DOHOS_PLATFORM)
    6. if(DEFINED PACKAGE_FIND_FILE)
    7. include(${PACKAGE_FIND_FILE})
    8. endif()
    9. include_directories(
    10. ${NATIVERENDER_ROOT_PATH}
    11. ${NATIVERENDER_ROOT_PATH}/include
    12. ${NATIVERENDER_ROOT_PATH}/render
    13. ${NATIVERENDER_ROOT_PATH}/manager
    14. )
    15. add_library(nativerender SHARED
    16. render/EGLRender.cpp
    17. render/egl_core.cpp
    18. render/plugin_render.cpp
    19. manager/plugin_manager.cpp
    20. napi_init.cpp
    21. )
    22. find_library(
    23. # 设置路径变量的名称。
    24. EGL-lib
    25. # 指定要让CMake查找的NDK库的名称。
    26. EGL
    27. )
    28. find_library(
    29. # 设置路径变量的名称。
    30. GLES-lib
    31. # 指定要让CMake查找的NDK库的名称。
    32. GLESv3
    33. )
    34. find_library(
    35. # 设置路径变量的名称。
    36. hilog-lib
    37. # 指定要让CMake查找的NDK库的名称。
    38. hilog_ndk.z
    39. )
    40. find_library(
    41. # 设置路径变量的名称。
    42. libace-lib
    43. # 指定要让CMake查找的NDK库的名称。
    44. ace_ndk.z
    45. )
    46. find_library(
    47. # 设置路径变量的名称。
    48. libnapi-lib
    49. # 指定要让CMake查找的NDK库的名称。
    50. ace_napi.z
    51. )
    52. find_library(
    53. # 设置路径变量的名称。
    54. libuv-lib
    55. # 指定要让CMake查找的NDK库的名称。
    56. uv
    57. )
    58. target_link_libraries(nativerender PUBLIC
    59. ${EGL-lib} ${GLES-lib} ${hilog-lib} ${libace-lib} ${libnapi-lib} ${libuv-lib} libnative_window.so)

    上述用例具体实现可参考NativeXComponent

在ArkTS侧使用SurfaceId进行渲染绘制

完整使用SurfaceId进行渲染绘制的示例及其主要开发场景如下:

  • 在ArkTS侧创建XComponent组件,并使用XComponentController来管理其持有的Surface生命周期。
  • 在onSurfaceCreated回调内获取surfaceId并将其传递给AVPlayer。
  • 使用surfaceId初始化AVPlayer,并为其设置必要信息,实现视频的播放。
说明

更多AVPlayer用法请参考AVPlayer

接口说明

展开
接口名 描述
onSurfaceCreated(surfaceId: string): void 当XComponent持有的Surface创建后进行该回调。

开发步骤

以下步骤展示如何在ArkTS侧创建SURFACE类型的XComponent,获取surfaceId,并将其设置给AVPlayer实现视频播放。

  1. 创建XComponent并传入XComponentController。

    收起
    自动换行
    深色代码主题
    复制
    1. XComponent({ type: XComponentType.SURFACE, controller: this.videoXComponentController })
  2. 在XComponentController中注册onSurfaceCreated生命周期,并在其中获取surfaceId,将获取到的surfaceId和待播的视频源信息传递给AVPlayer。

    收起
    自动换行
    深色代码主题
    复制
    1. class VideoXComponentController extends XComponentController {
    2. private avPlayerController: AVPlayerController;
    3. constructor(avPlayerController: AVPlayerController) {
    4. super();
    5. this.avPlayerController = avPlayerController;
    6. }
    7. onSurfaceCreated(surfaceId: string): void {
    8. let source: VideoData = {
    9. type: VideoDataType.RAW_FILE,
    10. videoSrc: 'videoTest.mp4'
    11. };
    12. // 将surfaceId和视频源信息传递给AVPlayer
    13. this.avPlayerController.initAVPlayer(source, surfaceId);
    14. }
    15. }
  3. 初始化AVPlayer。

    收起
    自动换行
    深色代码主题
    复制
    1. public async initAVPlayer(source: VideoData, surfaceId: string) {
    2. this.curSource = source;
    3. if (source.seekTime) {
    4. this.seekTime = source.seekTime;
    5. }
    6. if (source.isMuted) {
    7. this.isMuted = source.isMuted;
    8. }
    9. if (!this.curSource) {
    10. return;
    11. }
    12. this.surfaceID = surfaceId; // 存储surfaceId
    13. try {
    14. this.avPlayer = await media.createAVPlayer();
    15. this.setAVPlayerCallback();
    16. // 根据不同的视频文件格式设置视频源
    17. switch (this.curSource.type) {
    18. case VideoDataType.RAW_FILE:
    19. let fileDescriptor = await this.context?.resourceManager.getRawFd(this.curSource.videoSrc);
    20. this.avPlayer.fdSrc = fileDescriptor;
    21. break;
    22. case VideoDataType.URL:
    23. this.avPlayer.url = this.curSource.videoSrc;
    24. break;
    25. case VideoDataType.RAW_M3U8_FILE:
    26. // ...
    27. case VideoDataType.RAW_MAP4_FILE:
    28. // ...
    29. default:
    30. break;
    31. }
    32. } catch (err) {
    33. hilog.error(CommonConstants.LOG_DOMAIN, TAG,
    34. `InitPlayer failed, code is ${err.code}, message is ${err.message}`);
    35. }
    36. }
    37. private setAVPlayerCallback() {
    38. if (!this.avPlayer) {
    39. return;
    40. }
    41. this.avPlayer.on('durationUpdate', (time: number) => {
    42. AppStorage.setOrCreate('DurationTime', time); // 更新视频总时长
    43. });
    44. this.avPlayer.on('timeUpdate', (time: number) => {
    45. this.currentTime = time; // 更新当前进度
    46. AppStorage.setOrCreate('CurrentTime', time);
    47. });
    48. this.avPlayer.on('error', (err: BusinessError) => {
    49. if (!this.avPlayer) {
    50. return;
    51. }
    52. hilog.error(CommonConstants.LOG_DOMAIN, TAG,
    53. `Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
    54. this.avPlayer.reset().catch((err: BusinessError) => {
    55. hilog.error(CommonConstants.LOG_DOMAIN, TAG,
    56. `Reset failed, code is ${err.code}, message is ${err.message}`);
    57. });
    58. })
    59. this.setStateChangeCallback();
    60. }
    61. private setStateChangeCallback() {
    62. if (!this.avPlayer) {
    63. return;
    64. }
    65. this.avPlayer.on('stateChange', async (state) => {
    66. if (!this.avPlayer) {
    67. return;
    68. }
    69. switch (state) {
    70. case 'idle':
    71. hilog.info(CommonConstants.LOG_DOMAIN, TAG, `setAVPlayerCallback AVPlayer state idle called.`);
    72. break;
    73. case 'initialized':
    74. this.avPlayer.surfaceId = this.surfaceID; // 设置surfaceId,作为视频画面承载的画布
    75. this.avPlayer.prepare().catch((err: BusinessError) => {
    76. hilog.error(CommonConstants.LOG_DOMAIN, TAG,
    77. `prepare failed, code is ${err.code}, message is ${err.message}`);
    78. });
    79. break;
    80. case 'prepared':
    81. // ...
    82. // 实现自动播放
    83. this.avPlayer.play().catch((err: BusinessError) => {
    84. hilog.error(CommonConstants.LOG_DOMAIN, TAG, `play failed, code is ${err.code}, message is ${err.message}`);
    85. })
    86. break;
    87. case 'playing':
    88. this.isPlaying = true;
    89. break;
    90. case 'completed':
    91. this.currentTime = 0;
    92. break;
    93. default:
    94. break;
    95. }
    96. });
    97. }
搜索
请输入您想要搜索的关键词