本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新

一、介绍

XComponent 是鸿蒙中一个非常强大的组件,主要为图形绘制和媒体数据写入提供了 Surface 能力。允许将一个 Surface(可以理解为一块可供绘制的画布)嵌入到应用视图中:

  • 高性能绘制:它可以直接与底层图形系统交互,非常适合用于 EGL/OpenGL ES 渲染、视频播放相机预览等需要高性能图形处理的场景。

  • 灵活的类型:XComponent 支持多种类型,以适应不同的需求:

    类型 描述
    SURFACE 绘制的内容单独送显,直接合成到屏幕,性能最佳。
    COMPONENT XComponent 作为一个容器组件,可以在其中执行非UI逻辑或动态加载显示内容。
    TEXTURE 绘制内容会与 XComponent 组件的内容合成后展示到屏幕上。

二、如何使用

在 ArkTS 中,可以通过几种不同的接口来创建 XComponent 组件。

接口版本/特性 适用场景 关键配置
XComponent(options: XComponentOptions) (推荐) API 12+,功能最全面,支持 AI 分析。 通过 XComponentOptions 配置类型、控制器及 AI 选项。
XComponent(params: NativeXComponentParameters) API 19+,需将组件节点传递至 Native (C++) 侧进行高级操作时使用。 通过 NativeXComponentParameters 配置。
传统接口 (已废弃或不推荐) 早期版本,不推荐在新项目中使用。 通过 idtypelibraryname 等参数配置。

1、基本创建示例

// 引入XComponentController
import { XComponentController } from '@kit.ArkUI';

@Entry
@Component
struct GraphicsCanvas {
  // 实例化组件控制器
  private xComponentCtrl: XComponentController = new XComponentController();
  // 用于存储Surface ID
  private surfaceId: string = '';

  build() {
    Column() {
      // 使用XComponent组件
      XComponent({
        id: 'my_gl_surface',
        type: XComponentType.SURFACE, // 指定为SURFACE类型
        controller: this.xComponentCtrl // 绑定控制器
      })
      .onLoad(() => {
        // 组件加载完成时,获取Surface ID并设置尺寸
        this.surfaceId = this.xComponentCtrl.getXComponentSurfaceId();
        console.info(`XComponent Surface ID: ${this.surfaceId}`);
        // 在需要时设置Surface大小
        // this.xComponentCtrl.setXComponentSurfaceSize({surfaceWidth: 1920, surfaceHeight: 1080});
      })
      .onDestroy(() => {
        // 组件销毁时,可进行资源清理
        console.info('XComponent is being destroyed.');
      })
      .width('100%')
      .height('300px')
      .backgroundColor(Color.Black) // 设置背景色
    }
    .width('100%')
    .height('100%')
    .padding(10)
  }
}
  • XComponentController:这个控制器是关键,它提供了获取 Surface ID 等方法,这个 ID 对于后续与相机、Native 层渲染等模块对接至关重要。

  • onLoad 回调:当 XComponent 及其底层的 Surface 准备就绪时,会触发此回调,是进行初始化操作(如获取 Surface ID)的安全时机。

  • onDestroy 回调:当组件卸载时触发,用于释放资源。

2、动态创建

除了在 build 方法中声明,还可以通过 NodeController 动态创建 XComponent 节点,需要更精细控制节点生命周期时非常有用。

import { NodeController, FrameNode, XComponentNode, XComponentType, NodeRenderType } from "@ohos.arkui.node";
import { UIContext } from '@ohos.arkui.UIContext';

class CustomXComponentNodeController extends NodeController {
  private xCompNode: CustomXComponentNode | null = null;
  // 假设使用名为 "native_render" 的 Native 动态库
  private nativeLib: string = "native_render";

  constructor() {
    super();
  }

  // 创建并返回XComponentNode节点
  makeNode(uiContext: UIContext): FrameNode | null {
    this.xCompNode = new CustomXComponentNode(uiContext, {
      // 必须显式指定理想大小,否则节点可能不显示
      selfIdealSize: { width: 300, height: 300 }
    }, "xcomponent_node_id", XComponentType.SURFACE, this.nativeLib);
    return this.xCompNode;
  }

  // 示例:动态改变渲染类型
  switchRenderType(newType: NodeRenderType): void {
    if (this.xCompNode) {
      this.xCompNode.changeRenderType(newType);
    }
  }
}

// 自定义XComponentNode,可以覆写生命周期回调
class CustomXComponentNode extends XComponentNode {
  onCreate(event: Object) {
    // 节点加载完成回调,event中包含Native上下文
    console.info("CustomXComponentNode has been created.");
  }

  onDestroy() {
    // 节点销毁回调
    console.info("CustomXComponentNode is being destroyed.");
  }
}

@Entry
@Component
struct DynamicGraphicsScene {
  // 持有自定义NodeController的实例
  private nodeCtrl: CustomXComponentNodeController = new CustomXComponentNodeController();

  build() {
    Column() {
      // 使用NodeContainer来承载动态创建的XComponentNode
      NodeContainer(this.nodeCtrl)
        .width(300)
        .height(300)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}
  • 这种方式通过 NodeController 和 XComponentNode 提供了更强的程序化控制能力,例如动态改变渲染类型 (changeRenderType)。

       getXComponentSurfaceId(): string获取SurfaceId的关键方法

       getXComponentContext(): Object:获取与Native层交互的上下文。

  • 关键点:在 RenderOptions 中必须通过 selfIdealSize 明确指定 XComponentNode 的尺寸,否则其内容区域可能为零而导致不显示。

三、常见应用场景

    1、相机预览示例

  这是 XComponent 最典型的应用之一。其核心流程是:获取 Surface 的ID -> 将该ID传递给相机系统 -> 相机将图像数据填充到对应的 Surface

// 引入XComponentController和相机模块
import { XComponentController, XComponentType } from '@kit.ArkUI';
import camera from '@ohos.multimedia.camera';

@Entry
@Component
struct CameraPreview {
  // 创建控制器实例
  private xComponentCtrl: XComponentController = new XComponentController();
  // 用于存储Surface ID
  private surfaceId: string = '';

  build() {
    Column() {
      // 使用XComponent组件
      XComponent({
        id: 'camera_xcomponent',
        type: XComponentType.SURFACE, // 指定为SURFACE类型
        controller: this.xComponentCtrl // 绑定控制器
      })
      .onLoad(() => {
        // 组件加载完成,获取Surface ID
        this.surfaceId = this.xComponentCtrl.getXComponentSurfaceId();
        console.info(`Camera Surface ID: ${this.surfaceId}`);
        // 将surfaceId传递给相机API,创建预览输出
        camera.createPreviewOutput(this.surfaceId).then((previewOutput) => {
          console.info('PreviewOutput created successfully');
          // ... 后续可配置相机会话并启动预览
        }).catch((error) => {
          console.error(`Failed to create PreviewOutput: ${error.code}`);
        });
      })
      .onDestroy(() => {
        // 组件销毁时,停止预览并释放相机资源
        console.info('CameraPreview XComponent is being destroyed.');
      })
      .width('100%')
      .height('300px')
      .backgroundColor(Color.Black) // 可设置默认背景色
    }
    .width('100%')
    .height('100%')
  }
}
   2. 作为容器组件示例

 当 type 设置为 COMPONENT 时,XComponent 不再是一块画布,而变成一个可以容纳其他子组件的容器。它本身不支持直接的事件响应,但其内部的子组件可以。

import { XComponentType } from '@kit.ArkUI';

@Entry
@Component
struct CustomContainer {
  build() {
    Column() {
      // 作为容器的XComponent
      XComponent({
        id: 'container_component',
        type: XComponentType.COMPONENT
      }) {
        // 在容器内可以正常使用其他子组件
        Column() {
          Text('这是一个容器内部')
            .fontSize(20)
            .fontColor(Color.White)
          
          Button('点击我', { type: ButtonType.Normal })
            .backgroundColor(Color.Pink)
            .onClick(() => {
              console.info('容器内的按钮被点击了!');
            })
        }
        .justifyContent(FlexAlign.Center)
        .width('100%')
        .height(200)
        .backgroundColor(Color.Grey)
      }
      .width('90%')
      .margin({ top: 20 })
    }
    .width('100%')
    .height('100%')
    .padding(10)
  }
}
   3. 自定义图形绘制示例 (OpenGL ES)

  ArkTS侧 (xxx.ets) 主要负责UI布局和Native桥接:

import { XComponentController, XComponentType } from '@kit.ArkUI';

@Entry
@Component
struct OpenGLScene {
  private xComponentCtrl: XComponentController = new XComponentController();
  // 假设你的Native动态库名为 'libgl_render.so'
  private nativeLibrary: string = "gl_render";

  build() {
    Column() {
      // 关联到Native动态库的XComponent
      XComponent({
        id: 'gl_xcomponent',
        type: XComponentType.SURFACE,
        controller: this.xComponentCtrl,
        libraryname: this.nativeLibrary // 指定Native动态库名称
      })
      .onLoad(() => {
        // 通常,Surface准备就绪的信号和后续的渲染指令会通过NAPI传递给Native层
        console.info('OpenGL XComponent is loaded.');
        const surfaceId = this.xComponentCtrl.getXComponentSurfaceId();
        // 你可以通过NAPI调用,将surfaceId等参数传递给Native渲染引擎
      })
      .onDestroy(() => {
        // 通知Native层释放OpenGL资源
        console.info('OpenGL XComponent is being destroyed.');
      })
      .width('100%')
      .height('300px')
    }
    .width('100%')
    .height('100%')
  }
}

C++侧 (Native层) 则负责核心的图形API调用和渲染工作:

  1. 初始化:在 OnSurfaceCreated 回调中初始化EGL环境、创建OpenGL上下文。

  2. 渲染:在 OnSurfaceChanged (处理尺寸变化) 和渲染循环中,使用OpenGL ES命令进行绘制。

  3. 提交:通过 OH_NativeWindow_NativeWindowRequestBuffer 获取 OHNativeWindowBuffer,将渲染好的图像数据写入,最后通过 OH_NativeWindow_NativeWindowFlushBuffer 提交到屏幕。

对于需要自主控制每一帧画面的场景(如游戏、3D建模、数据可视化),XComponent 可以与 Native (C++) 层的 OpenGL ES 结合,实现高性能绘制。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐