【HarmonyOS】富文本编辑器RichEditor详解

【HarmonyOS】富文本编辑器RichEditor详解

一、前言

在信息化高速发展的今天,普通的文本容器,已经不能够承载用户丰富的表达欲。富文本展示已经是移动开发中,必备要解决的问题,在鸿蒙中,通过在系统层提供RichEditor控件,来解决富文本展示的问题。

HarmonyOS推出的RichEditor控件,提供了从基础文本输入到复杂图文混排的完整解决方案。

从API version 10开始支持的RichEditor控件,不仅具备文本输入、样式设置等基础能力,还创新性地支持自定义键盘、图文混排、事件回调等高级特性。

随着版本迭代,RichEditor不断进化,从API version 11开始支持元服务调用,到API version 20引入AI菜单和撤销样式保留等功能,已发展为一个成熟稳定的富文本解决方案。

本文将从实际使用流程和完整实战Demo出发,详细解析RichEditor控件的核心功能与应用场景,帮助开发者快速掌握这一强大工具的使用方法。

二、使用流程

1、组件创建方式

RichEditor控件提供了两种创建方式:

(1)使用属性字符串构建

这种方式一般用于比较简单的富文本场景,例如上图颜色不同的一段话。

基于属性字符串(StyledString/MutableStyledString)构建,持有属性字符串对象来管理数据,通过修改属性字符串对象的内容、样式,再传递给组件,实现对富文本组件内容的更新。
相比于使用controller接口进行内容样式更新,使用起来更加灵活便捷。

typescript 复制代码
@Entry
@Component
struct Index {

  // 定义字体样式对象,设置字体颜色为粉色
  fontStyle: TextStyle = new TextStyle({
    fontColor: Color.Pink
  });

  // 创建可变样式字符串,用于存储富文本内容及其样式
  // 初始文本为"使用属性字符串构建的RichEditor组件"
  // 并为前5个字符("使用属性字")应用上面定义的粉色字体样式
  mutableStyledString: MutableStyledString = new MutableStyledString("使用属性字符串构建的RichEditor组件",
    [{
      start: 0,          // 样式起始位置(从0开始)
      length: 5,         // 样式作用的字符长度
      styledKey: StyledStringKey.FONT,  // 样式类型为字体样式
      styledValue: this.fontStyle       // 具体的样式值
    }]);

  // 初始化属性字符串模式的RichEditor控制器
  // 该控制器专门用于处理基于属性字符串的富文本操作
  controller: RichEditorStyledStringController = new RichEditorStyledStringController();

  // 配置RichEditor组件的选项,将控制器传入
  options: RichEditorStyledStringOptions = { controller: this.controller };


  build() {
    Column() {
      // 构建RichEditor组件,使用上面配置的选项
      RichEditor(this.options)
        // 组件初始化完成回调
        // 当RichEditor组件准备好后,将之前创建的可变样式字符串设置到编辑器中
        .onReady(() => {
          this.controller.setStyledString(this.mutableStyledString);
        })
    }
    .height('100%')  // Column高度占满整个父容器
    .width('100%')   // Column宽度占满整个父容器
    .justifyContent(FlexAlign.Center)  // 垂直方向居中对齐子组件
  }
}
(2)使用RichEditorController构建

这种方式一般用于复杂内容场景,通过RichEditorController提供的接口实现内容、样式的管理。

typescript 复制代码
@Entry
@Component
struct IndexPage2 {
  // 初始化富文本编辑器控制器,用于管理RichEditor组件
  controller: RichEditorController = new RichEditorController();

  // 配置RichEditor组件选项,传入控制器实例
  options: RichEditorOptions = { controller: this.controller };

  build() {
    Column() {
      Column() {
        // 创建RichEditor组件并应用配置选项
        RichEditor(this.options)
          // 组件初始化完成回调,用于设置初始内容
          .onReady(() => {
            // 1. 添加第一段文本内容
            // 使用addTextSpan方法添加文本,并设置橙色字体、16px大小
            this.controller.addTextSpan('使用RichEditorController', {
              style: {
                fontColor: Color.Orange,
                fontSize: 16
              }
            });

            // 2. 添加符号内容
            // 使用addSymbolSpan方法添加系统内置符号(篮球图标)
            // 设置符号大小为30px
            this.controller.addSymbolSpan($r("sys.symbol.basketball_fill"), {
              style: {
                fontSize: 30
              }
            });

            // 3. 添加第二段文本内容
            // 使用addTextSpan方法添加文本,并设置红色字体、20px大小
            this.controller.addTextSpan('构建富文本!!!', {
              style: {
                fontColor: Color.Red,
                fontSize: 20
              }
            });
          })
      }.width('100%')  // 内部Column宽度占满父容器
    }.height('100%')  // 外部Column高度占满父容器
  }
}

2、组件的属性配置参数效果

RichEditor提供了丰富的属性来定制编辑体验,下面介绍几个常用属性的配置方法。

(1)自定义选择菜单

通过bindSelectionMenu属性可以设置自定义选择菜单,替代组件默认的文本选择菜单,实现更丰富的菜单功能,如翻译、加粗等。

typescript 复制代码
// 自定义菜单构建器
@Builder
CustomMenu() {
  Column() {
    Menu() {
      MenuItemGroup() {
        MenuItem({
          startIcon: $r('app.media.icon_bold'),
          content: "加粗"
        })
        MenuItem({
          startIcon: $r('app.media.icon_italic'),
          content: "斜体"
        })
        MenuItem({
          startIcon: $r('app.media.icon_underline'),
          content: "下划线"
        })
      }
    }
    .radius(8)
    .backgroundColor(Color.White)
    .width(200)
  }
}

// 在RichEditor中绑定自定义菜单
RichEditor(this.options)
  .onReady(() => {
    this.controller.addTextSpan('长按触发自定义菜单', {
      style: {
        fontColor: Color.Black,
        fontSize: 16
      }
    })
  })
  .bindSelectionMenu(RichEditorSpanType.TEXT, this.CustomMenu, ResponseType.LongPress)
  .width(300)
  .height(200)
(2)光标和手柄颜色设置

通过caretColor属性可以设置输入框光标和手柄的颜色,提高视觉辨识度,使光标颜色与应用整体风格相协调。

typescript 复制代码
RichEditor(this.options)
  .onReady(() => {
    this.controller.addTextSpan('设置了橙色光标和手柄的富文本', {
      style: {
        fontColor: Color.Black,
        fontSize: 16
      }
    })
  })
  .caretColor(Color.Orange)
  .width(300)
  .height(100)
(3)占位文本设置

通过placeholder属性可以设置无输入时的提示文本,引导用户正确操作。

typescript 复制代码
RichEditor(this.options)
  .placeholder("请输入您的内容...", {
    fontColor: Color.Gray,
    font: {
      size: 14,
      family: "HarmonyOS Sans"
    }
  })
  .width(300)
  .height(80)

3、组件的事件监听与交互控制逻辑

RichEditor提供了丰富的事件监听接口,实现更灵活的编辑交互逻辑。

(1)初始化完成事件

初始化回调函数,一般在这里进行数据的加载,或者组件文本的拼接等。

typescript 复制代码
RichEditor(this.options)
  .onReady(() => {
    console.info('RichEditor初始化完成');

  })
(2)选择变化事件

内容选择区域或光标位置变化时触发,可用于实时更新工具栏状态。

typescript 复制代码
RichEditor(this.options)
  .onSelectionChange((range) => {
    console.info(`选中范围变化: start=${range.start}, end=${range.end}`);
    // 根据选中范围更新工具栏按钮状态
    this.updateToolbarState(range);
  })
(3)粘贴事件

粘贴操作前触发,可用于自定义粘贴内容处理。

typescript 复制代码
RichEditor(this.options)
  .onPaste((event) => {
    // 阻止默认粘贴行为
    event.preventDefault();
    // 自定义粘贴处理逻辑
    this.handleCustomPaste(event);
  })

4、内容操作与管理

通过控制器可以实现对编辑内容的程序化操作。

添加文本内容
typescript 复制代码
// 添加普通文本
this.controller.addTextSpan('新添加的文本内容', {
  style: {
    fontSize: 16,
    fontColor: Color.Blue
  }
});

// 在指定位置添加文本
this.controller.addTextSpan('在指定位置添加的文本', {
  style: {
    fontSize: 16,
    fontStyle: FontStyle.Italic
  },
  offset: 10 // 在偏移量10的位置添加
});
5、添加图片内容
typescript 复制代码
this.controller.addImageSpan($r('app.media.image'), {
  imageStyle: {
    size: [300, 200], // 图片大小
    objectFit: ImageFit.Contain, // 图片缩放类型
    verticalAlign: ImageSpanAlignment.MIDDLE // 垂直对齐方式
  }
});
6、更新文本样式
typescript 复制代码
// 更新指定范围的文本样式
this.controller.updateSpanStyle({
  start: 0,
  end: 5,
  textStyle: {
    fontWeight: 700, // 加粗
    decoration: {
      type: TextDecorationType.Underline, // 下划线
      color: Color.Red
    }
  }
});

三、DEMO源码

DEMO实现了一个富文本编辑器界面,支持字体样式设置、段落缩进控制、内容选中与编辑等功能,并通过自定义标记生成器实现列表缩进的可视化展示。

typescript 复制代码
const canvasWidth = 1000;
const canvasHeight = 100;
const Indentation = 40;

// 段落缩进标记生成器类
class LeadingMarginCreator {
  private settings: RenderingContextSettings = new RenderingContextSettings(true); // 渲染上下文设置
  private offscreenCanvas: OffscreenCanvas = new OffscreenCanvas(canvasWidth, canvasHeight); // 离屏画布
  private offContext: OffscreenCanvasRenderingContext2D = this.offscreenCanvas.getContext("2d", this.settings); // 离屏画布渲染上下文
  public static instance: LeadingMarginCreator = new LeadingMarginCreator(); // 单例实例

  // 获得字体字号级别(0-4级)
  public getFontSizeLevel(fontSize: number) {
    const fontScaled: number = Number(fontSize) / 16; // 字体缩放比例(相对于16px基准)

    enum FontSizeScaleThreshold {
      SMALL = 0.9,      // 小字体阈值
      NORMAL = 1.1,     // 正常字体阈值
      LEVEL_1_LARGE = 1.2, // 1级大字体阈值
      LEVEL_2_LARGE = 1.4, // 2级大字体阈值
      LEVEL_3_LARGE = 1.5  // 3级大字体阈值
    }

    let fontSizeLevel: number = 1; // 初始字号级别为1

    // 根据缩放比例确定字号级别
    if (fontScaled < FontSizeScaleThreshold.SMALL) {
      fontSizeLevel = 0;
    } else if (fontScaled < FontSizeScaleThreshold.NORMAL) {
      fontSizeLevel = 1;
    } else if (fontScaled < FontSizeScaleThreshold.LEVEL_1_LARGE) {
      fontSizeLevel = 2;
    } else if (fontScaled < FontSizeScaleThreshold.LEVEL_2_LARGE) {
      fontSizeLevel = 3;
    } else if (fontScaled < FontSizeScaleThreshold.LEVEL_3_LARGE) {
      fontSizeLevel = 4;
    } else {
      fontSizeLevel = 1;
    }

    return fontSizeLevel;
  }

  // 获得缩进级别比例(根据缩进宽度计算比例)
  public getmarginLevel(width: number) {
    let marginlevel: number = 1; // 初始缩进比例为1
    // 根据不同缩进宽度设置对应的比例
    if (width === 40) {
      marginlevel = 2.0;
    } else if (width === 80) {
      marginlevel = 1.0;
    } else if (width === 120) {
      marginlevel = 2/3;
    } else if (width === 160) {
      marginlevel = 0.5;
    } else if (width === 200) {
      marginlevel = 0.4;
    }
    return marginlevel;
  }

  // 生成文本标记(将文本转换为像素图)
  public genStrMark(fontSize: number, str: string): PixelMap {
    this.offContext = this.offscreenCanvas.getContext("2d", this.settings); // 重新获取渲染上下文
    this.clearCanvas(); // 清空画布
    this.offContext.font = fontSize + 'vp sans-serif'; // 设置字体样式
    this.offContext.fillText(str + '.', 0, fontSize * 0.9); // 绘制文本(末尾加点以确保宽度)
    // 获取像素图(根据文本长度计算宽度)
    return this.offContext.getPixelMap(0, 0, fontSize * (str.length + 1) / 1.75, fontSize);
  }

  // 生成方形标记(绘制正方形并转换为像素图)
  public genSquareMark(fontSize: number): PixelMap {
    this.offContext = this.offscreenCanvas.getContext("2d", this.settings); // 重新获取渲染上下文
    this.clearCanvas(); // 清空画布
    const coordinate = fontSize * (1 - 1 / 1.5) / 2; // 计算起始坐标
    const sideLength = fontSize / 1.5; // 计算正方形边长
    this.offContext.fillRect(coordinate, coordinate, sideLength, sideLength); // 绘制正方形
    // 获取正方形像素图
    return this.offContext.getPixelMap(0, 0, fontSize, fontSize);
  }

  // 生成圆圈符号标记(根据缩进级别、字体大小等参数绘制圆形标记)
  public genCircleMark(fontSize: number, width: number, level?: number): PixelMap {
    const indentLevel = level ?? 1; // 缩进级别(默认1)
    const offsetLevel = [22, 28, 32, 34, 38]; // 不同字号级别的垂直偏移量
    const fontSizeLevel = this.getFontSizeLevel(fontSize); // 获取字号级别
    const marginlevel = this.getmarginLevel(width); // 获取缩进比例
    const newCanvas = new OffscreenCanvas(canvasWidth, canvasHeight); // 创建新的离屏画布
    const newOffContext: OffscreenCanvasRenderingContext2D = newCanvas.getContext("2d", this.settings); // 新画布的渲染上下文
    const centerCoordinate = 50; // 圆心水平坐标基准
    const radius = 10; // 圆半径基准
    this.clearCanvas(); // 清空画布
    // 绘制椭圆(根据参数计算位置和大小)
    newOffContext.ellipse(
      100 * (indentLevel + 1) - centerCoordinate * marginlevel, // 圆心x坐标
      offsetLevel[fontSizeLevel], // 圆心y坐标(根据字号级别)
      radius * marginlevel, // 水平半径(根据缩进比例)
      radius, // 垂直半径
      0, 0, 2 * Math.PI // 椭圆参数(起始角度、结束角度)
    );
    newOffContext.fillStyle = '66FF0000'; // 填充颜色(半透明红色)
    newOffContext.fill(); // 填充图形
    // 获取圆形标记的像素图(根据缩进级别计算宽度)
    return newOffContext.getPixelMap(0, 0, 100 + 100 * indentLevel, 100);
  }

  private clearCanvas() {
    this.offContext.clearRect(0, 0, canvasWidth, canvasHeight); // 清空画布
  }
}

@Entry
@Component
struct IndexPage3 {
  // 富文本控制器(用于操作编辑器内容和样式)
  controller: RichEditorController = new RichEditorController();
  options: RichEditorOptions = { controller: this.controller }; // 富文本编辑器选项

  // 缩进标记生成器实例(使用单例模式)
  private leadingMarkCreatorInstance = LeadingMarginCreator.instance;
  private fontNameRawFile: string = 'MiSans-Bold'; // 自定义字体名称

  // 状态变量(用于界面交互和数据展示)
  @State fs: number = 30; // 字体大小
  @State cl: number = Color.Black; // 字体颜色
  @State start: number = -1; // 选中起始位置
  @State end: number = -1; // 选中结束位置
  @State message: string = "[-1, -1]"; // 选中范围提示信息
  @State content: string = ""; // 选中内容
  private leftMargin: Dimension = 0; // 左缩进量
  private richEditorTextStyle: RichEditorTextStyle = {}; // 富文本样式

  // 新增:光标颜色和选中背景色状态
  @State cursorColor: Color|string = Color.Black; // 光标颜色
  @State selectionColor: Color|string = Color.Gray; // 选中背景色

  aboutToAppear() {
    // 注册自定义字体(应用启动时加载字体文件)
    this.getUIContext().getFont().registerFont({
      familyName: 'MiSans-Bold',
      familySrc: '/font/MiSans-Bold.ttf'
    });
  }

  build() {
    Scroll() {
      Column() {
        // 颜色控制区域(切换界面主题颜色)
        Row() {
          Button("红色主题").onClick(() => {
            this.cursorColor = Color.Red; // 设置红色光标
            this.selectionColor = "#FFCCCC"; // 设置红色选中背景
          }).width("30%");

          Button("绿色主题").onClick(() => {
            this.cursorColor = Color.Green; // 设置绿色光标
            this.selectionColor = "#CCFFCC"; // 设置绿色选中背景
          }).width("30%");

          Button("蓝色主题").onClick(() => {
            this.cursorColor = Color.Blue; // 设置蓝色光标
            this.selectionColor = "#CCCCFF"; // 设置蓝色选中背景
          }).width("30%");
        }
        .width("100%")
        .justifyContent(FlexAlign.SpaceBetween)
        .margin({ bottom: 10 });

        // 选中范围和内容显示区域(展示当前选中的位置和内容)
        Column() {
          Text("selection range:").width("100%").fontSize(16); // 选中范围标题
          Text() {
            Span(this.message) // 显示选中范围信息
          }.width("100%").fontSize(16);
          Text("selection content:").width("100%").fontSize(16); // 选中内容标题
          Text() {
            Span(this.content) // 显示选中内容
          }.width("100%").fontSize(16);
        }
        .borderWidth(1)
        .borderColor(Color.Red)
        .width("100%")
        .padding(10)
        .margin({ bottom: 10 });

        // 样式操作按钮区域(对选中内容进行样式修改)
        Row() {
          Button("加粗").onClick(() => {
            // 更新选中区域文本样式(设置为加粗)
            this.controller.updateSpanStyle({
              start: this.start,
              end: this.end,
              textStyle: { fontWeight: FontWeight.Bolder }
            });
          }).width("25%");

          Button("获取选中内容").onClick(() => {
            this.content = ""; // 清空内容显示
            // 获取选中范围内的所有文本片段
            this.controller.getSpans({ start: this.start, end: this.end }).forEach(item => {
              if (typeof(item as RichEditorImageSpanResult)['imageStyle'] !== 'undefined') {
                // 处理图片片段
                this.content += (item as RichEditorImageSpanResult).valueResourceStr + "\n";
              } else {
                if (typeof(item as RichEditorTextSpanResult)['symbolSpanStyle'] !== 'undefined') {
                  // 处理符号片段(显示字号)
                  this.content += (item as RichEditorTextSpanResult).symbolSpanStyle?.fontSize + "\n";
                } else {
                  // 处理普通文本片段(显示文本内容)
                  this.content += (item as RichEditorTextSpanResult).value + "\n";
                }
              }
            });
          }).width("25%");

          Button("删除选中内容").onClick(() => {
            // 删除选中区域内容
            this.controller.deleteSpans({ start: this.start, end: this.end });
            this.start = -1; // 重置选中起始位置
            this.end = -1; // 重置选中结束位置
            this.message = "[" + this.start + ", " + this.end + "]"; // 更新选中范围提示
          }).width("25%");

          Button("设置样式1").onClick(() => {
            // 设置输入时的默认样式
            this.controller.setTypingStyle({
              fontWeight: 'medium', // 中等粗细
              fontFamily: this.fontNameRawFile, // 自定义字体
              fontColor: Color.Blue, // 蓝色
              fontSize: 50, // 字号50
              fontStyle: FontStyle.Italic, // 斜体
              decoration: { type: TextDecorationType.Underline, color: Color.Green } // 绿色下划线
            });
          }).width("25%");
        }
        .borderWidth(1)
        .borderColor(Color.Red)
        .width("100%")
        .height("10%")
        .margin({ bottom: 10 });

        // 富文本编辑器区域(核心编辑界面)
        Column() {
          RichEditor(this.options)
            .onReady(() => {
              // 编辑器准备就绪时初始化内容
              this.controller.addTextSpan("0123456789\n", {
                style: {
                  fontWeight: 'medium', // 中等粗细
                  fontFamily: this.fontNameRawFile, // 自定义字体
                  fontColor: Color.Red, // 红色
                  fontSize: 50, // 字号50
                  fontStyle: FontStyle.Italic, // 斜体
                  decoration: { type: TextDecorationType.Underline, color: Color.Green } // 绿色下划线
                }
              });

              this.controller.addTextSpan("abcdefg", {
                style: {
                  fontWeight: FontWeight.Lighter, // 更细
                  fontFamily: 'HarmonyOS Sans', // HarmonyOS默认字体
                  fontColor: 'rgba(0,128,0,0.5)', // 半透明绿色
                  fontSize: 30, // 字号30
                  fontStyle: FontStyle.Normal, // 正常样式
                  decoration: { type: TextDecorationType.Overline, color: 'rgba(169, 26, 246, 0.50)' } // 半透明紫色上划线
                }
              });
            })
            .onSelect((value: RichEditorSelection) => {
              // 选中事件回调(更新选中范围状态)
              this.start = value.selection[0];
              this.end = value.selection[1];
              this.message = "[" + this.start + ", " + this.end + "]";
            })
            .caretColor(this.cursorColor)  // 设置光标颜色(来自状态变量)
            .selectedBackgroundColor(this.selectionColor)  // 设置选中背景色(来自状态变量)
            .borderWidth(1)
            .borderColor(Color.Green)
            .width("100%")
            .height("30%")
            .margin({ bottom: 10 });
        }
        .borderWidth(1)
        .borderColor(Color.Red)
        .width("100%")
        .padding(10);

        // 缩进操作按钮区域(控制段落缩进)
        Column() {
          Row({ space: 5 }) {
            Button("向右列表缩进").onClick(() => {
              let margin = Number(this.leftMargin); // 当前左缩进量
              if (margin < 200) {
                margin += Indentation; // 增加缩进量(40像素)
                this.leftMargin = margin;
              }
              // 更新段落样式(设置带标记的缩进)
              this.controller.updateParagraphStyle({
                start: -10,
                end: -10,
                style: {
                  leadingMargin: {
                    pixelMap: this.leadingMarkCreatorInstance.genCircleMark(100, margin, 1), // 圆形缩进标记
                    size: [margin, 40] // 缩进标记大小
                  }
                }
              });
            }).width("48%");

            Button("向左列表缩进").onClick(() => {
              let margin = Number(this.leftMargin); // 当前左缩进量
              if (margin > 0) {
                margin -= Indentation; // 减少缩进量(40像素)
                this.leftMargin = margin;
              }
              // 更新段落样式(设置带标记的缩进)
              this.controller.updateParagraphStyle({
                start: -10,
                end: -10,
                style: {
                  leadingMargin: {
                    pixelMap: this.leadingMarkCreatorInstance.genCircleMark(100, margin, 1), // 圆形缩进标记
                    size: [margin, 40] // 缩进标记大小
                  }
                }
              });
            }).width("48%");
          }
          .margin({ bottom: 10 });

          Row({ space: 5 }) {
            Button("向右空白缩进").onClick(() => {
              let margin = Number(this.leftMargin); // 当前左缩进量
              if (margin < 200) {
                margin += Indentation; // 增加缩进量(40像素)
                this.leftMargin = margin;
              }
              // 更新段落样式(设置纯空白缩进)
              this.controller.updateParagraphStyle({
                start: -10,
                end: -10,
                style: { leadingMargin: margin } // 仅设置缩进宽度
              });
            }).width("48%");

            Button("向左空白缩进").onClick(() => {
              let margin = Number(this.leftMargin); // 当前左缩进量
              if (margin > 0) {
                margin -= Indentation; // 减少缩进量(40像素)
                this.leftMargin = margin;
              }
              // 更新段落样式(设置纯空白缩进)
              this.controller.updateParagraphStyle({
                start: -10,
                end: -10,
                style: { leadingMargin: margin } // 仅设置缩进宽度
              });
            }).width("48%");
          }
          .margin({ bottom: 10 });

          Button("获取当前样式").onClick(() => {
            this.richEditorTextStyle = this.controller.getTypingStyle();
            console.info("RichEditor getTypingStyle:" + JSON.stringify(this.richEditorTextStyle));
          })
            .width("100%")
            .margin({ bottom: 10 });
        }
        .width("100%")
        .padding(10);
      }
      .width("100%")
      .padding(10);
    }
  }
}
相关推荐
一只栖枝6 小时前
华为 HCIE 大数据认证中 Linux 命令行的运用及价值
大数据·linux·运维·华为·华为认证·hcie·it
zhanshuo10 小时前
在鸿蒙里优雅地处理网络错误:从 Demo 到实战案例
harmonyos
zhanshuo10 小时前
在鸿蒙中实现深色/浅色模式切换:从原理到可运行 Demo
harmonyos
whysqwhw15 小时前
鸿蒙分布式投屏
harmonyos
whysqwhw16 小时前
鸿蒙AVSession Kit
harmonyos
whysqwhw18 小时前
鸿蒙各种生命周期
harmonyos
whysqwhw19 小时前
鸿蒙音频编码
harmonyos
whysqwhw19 小时前
鸿蒙音频解码
harmonyos
whysqwhw19 小时前
鸿蒙视频解码
harmonyos
whysqwhw19 小时前
鸿蒙视频编码
harmonyos