文本选择菜单,适用于富文本组件通过[bindSelectionMenu]绑定自定义文本选择菜单,建议绑定鼠标右键或者鼠标选中方式弹出,不支持作为普通组件单独使用。

说明

该组件从API Version 11开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。

导入模块

import { SelectionMenu, EditorMenuOptions, ExpandedMenuOptions, EditorEventInfo, SelectionMenuOptions } from '@kit.ArkUI'

子组件

无。

SelectionMenu

SelectionMenu(options: SelectionMenuOptions): void

入参为空时,文本选择菜单组件SelectionMenu内容区大小及组件大小为零。表现例如,富文本组件[RichEditor]使用[bindSelectionMenu]接口绑定一个SelectionMenu的右键菜单,则右键富文本组件区域时无任何菜单弹出。

装饰器类型: @Builder

元服务API: 从API version 12开始,该接口支持在元服务中使用。

系统能力: SystemCapability.ArkUI.ArkUI.Full

参数:

参数名 类型 必填 说明
options [SelectionMenuOptions] 文本选择菜单可选项。

SelectionMenuOptions

SelectionMenuOptions定义SelectionMenu的可选菜单类型项及其具体配置参数。

元服务API: 从API version 12开始,该接口支持在元服务中使用。

系统能力: SystemCapability.ArkUI.ArkUI.Full

名称 类型 必填 说明
editorMenuOptions Array<[EditorMenuOptions]> 编辑菜单。editorMenuOptions未配置时,不显示编辑菜单。同时配置EditorMenuOptions中action和builder时,点击图标会同时响应。点击编辑菜单图标默认不关闭整个菜单,应用可以通过action接口配置RichEditorController的closeSelectionMenu主动关闭菜单。
expandedMenuOptions Array<[ExpandedMenuOptions]> 扩展下拉菜单。expandedMenuOptions参数为空时无更多按钮,不显示扩展下拉菜单。expandedMenuOptions参数不为空时显示更多按钮,配置菜单项收起在更多按钮中,点击更多按钮展示。
controller [RichEditorController] 富文本控制器不为空时显示默认系统菜单(包含剪切复制粘贴等部分)且默认菜单功能内置。controller为空时不显示更多按钮,expandedMenuOptions参数不为空则显示下拉菜单中。系统默认只支持复制粘贴富文本文本内容,图文混排需要应用自定义onCopy、onPaste接口。应用自行配置onCopy
onCopy (event?: [EditorEventInfo]) => void 替代内置系统菜单复制项的事件回调。生效前提是一定要有controller参数,有系统默认菜单才能替换内置复制功能。**说明:**event为返回信息。
onPaste (event?: [EditorEventInfo]) => void 替代内置系统菜单粘贴项的事件回调。生效前提是一定要有controller参数,有系统默认菜单才能替换内置粘贴功能。**说明:**event为返回信息。
onCut (event?: [EditorEventInfo]) => void 替代内置系统菜单剪切项的事件回调。生效前提是一定要有controller参数,有系统默认菜单才能替换内置剪切功能。**说明:**event为返回信息。
onSelectAll (event?: [EditorEventInfo]) => void 替代内置系统菜单全选项的事件回调。生效前提是一定要有controller参数,有系统默认菜单才能替换内置全选功能。**说明:**event为返回信息。

EditorMenuOptions

编辑菜单选项。

元服务API: 从API version 12开始,该接口支持在元服务中使用。

系统能力: SystemCapability.ArkUI.ArkUI.Full

名称 类型 必填 说明
icon [ResourceStr] 图标资源。
builder () => void 点击时显示用户自定义组件,自定义组件在构造时结合@Builder使用。
action () => void 点击菜单项的事件回调。

ExpandedMenuOptions

扩展下拉菜单。

继承于[MenuItemOptions]。

元服务API: 从API version 12开始,该接口支持在元服务中使用。

系统能力: SystemCapability.ArkUI.ArkUI.Full

名称 类型 必填 说明
action () => void 点击菜单项的事件回调。

EditorEventInfo

选中内容信息。

元服务API: 从API version 12开始,该接口支持在元服务中使用。

系统能力: SystemCapability.ArkUI.ArkUI.Full

名称 类型 必填 说明
content [RichEditorSelection] 选中内容信息。

属性

不支持[通用属性],宽度默认224vp, 高度自适应内容。

事件

不支持[通用事件]。

示例

该示例展示了文本绑定不同触发方式的自定义文本选择菜单的效果。

import { SelectionMenu, EditorMenuOptions, ExpandedMenuOptions, EditorEventInfo, SelectionMenuOptions } from '@kit.ArkUI'

@Entry
@Component
struct Index {
  @State select: boolean = true
  controller: RichEditorController = new RichEditorController();
  options: RichEditorOptions = { controller: this.controller }
  @State message: string = 'Hello world'
  @State textSize: number = 30
  @State fontWeight: FontWeight = FontWeight.Normal
  @State start: number = -1
  @State end: number = -1
  @State visibleValue: Visibility = Visibility.Visible
  @State colorTransparent: Color = Color.Transparent
  @State textStyle: RichEditorTextStyle = {}
  private editorMenuOptions: Array<EditorMenuOptions> =
    [
      { icon: $r("app.media.ic_notepad_textbold"), action: () => {
        if (this.controller) {
          let selection = this.controller.getSelection();
          let spans = selection.spans
          spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
            if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
              let span = item as RichEditorTextSpanResult
              this.textStyle = span.textStyle
              let start = span.offsetInSpan[0]
              let end = span.offsetInSpan[1]
              let offset = span.spanPosition.spanRange[0]
              if (this.textStyle.fontWeight != 11) {
                this.textStyle.fontWeight = FontWeight.Bolder
              } else {
                this.textStyle.fontWeight = FontWeight.Normal
              }
              this.controller.updateSpanStyle({
                start: offset + start,
                end: offset + end,
                textStyle: this.textStyle
              })
            }
          })
        }
      } },
      { icon: $r("app.media.ic_notepad_texttilt"), action: () => {
        if (this.controller) {
          let selection = this.controller.getSelection();
          let spans = selection.spans
          spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
            if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
              let span = item as RichEditorTextSpanResult
              this.textStyle = span.textStyle
              let start = span.offsetInSpan[0]
              let end = span.offsetInSpan[1]
              let offset = span.spanPosition.spanRange[0]
              if (this.textStyle.fontStyle == FontStyle.Italic) {
                this.textStyle.fontStyle = FontStyle.Normal
              } else {
                this.textStyle.fontStyle = FontStyle.Italic
              }
              this.controller.updateSpanStyle({
                start: offset + start,
                end: offset + end,
                textStyle: this.textStyle
              })
            }
          })
        }
      } },
      { icon: $r("app.media.ic_notepad_underline"),
        action: () => {
          if (this.controller) {
            let selection = this.controller.getSelection();
            let spans = selection.spans
            spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
              if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
                let span = item as RichEditorTextSpanResult
                this.textStyle = span.textStyle
                let start = span.offsetInSpan[0]
                let end = span.offsetInSpan[1]
                let offset = span.spanPosition.spanRange[0]
                if (this.textStyle.decoration) {
                  if (this.textStyle.decoration.type == TextDecorationType.Underline) {
                    this.textStyle.decoration.type = TextDecorationType.None
                  } else {
                    this.textStyle.decoration.type = TextDecorationType.Underline
                  }
                } else {
                  this.textStyle.decoration = { type: TextDecorationType.Underline, color: Color.Black }
                }
                this.controller.updateSpanStyle({
                  start: offset + start,
                  end: offset + end,
                  textStyle: this.textStyle
                })
              }
            })
          }
        }
      },
      { icon: $r("app.media.app_icon"), action: () => {
      }, builder: (): void => this.sliderPanel() },
      { icon: $r("app.media.ic_notepad_textcolor"), action: () => {
        if (this.controller) {
          let selection = this.controller.getSelection();
          let spans = selection.spans
          spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
            if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
              let span = item as RichEditorTextSpanResult
              this.textStyle = span.textStyle
              let start = span.offsetInSpan[0]
              let end = span.offsetInSpan[1]
              let offset = span.spanPosition.spanRange[0]
              if (this.textStyle.fontColor == Color.Orange || this.textStyle.fontColor == '#FFFFA500') {
                this.textStyle.fontColor = Color.Black
              } else {
                this.textStyle.fontColor = Color.Orange
              }
              this.controller.updateSpanStyle({
                start: offset + start,
                end: offset + end,
                textStyle: this.textStyle
              })
            }
          })
        }
      } }]
  private expandedMenuOptions: Array<ExpandedMenuOptions> =
    [{ startIcon: $r("app.media.icon"), content: '词典', action: () => {
    } }, { startIcon: $r("app.media.icon"), content: '翻译', action: () => {
    } }, { startIcon: $r("app.media.icon"), content: '搜索', action: () => {
    } }]
  private expandedMenuOptions1: Array<ExpandedMenuOptions> = []
  private editorMenuOptions1: Array<EditorMenuOptions> = []
  private selectionMenuOptions: SelectionMenuOptions = {
    editorMenuOptions: this.editorMenuOptions,
    expandedMenuOptions: this.expandedMenuOptions,
    controller: this.controller,
    onCut: (event?: EditorEventInfo) => {
      if (event && event.content) {
        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
            let span = item as RichEditorTextSpanResult
            console.info('test cut' + span.value)
            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1])
          }
        })
      }
    },
    onPaste: (event?: EditorEventInfo) => {
      if (event && event.content) {
        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
            let span = item as RichEditorTextSpanResult
            console.info('test onPaste' + span.value)
            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1])
          }
        })
      }
    },
    onCopy: (event?: EditorEventInfo) => {
      if (event && event.content) {
        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
            let span = item as RichEditorTextSpanResult
            console.info('test cut' + span.value)
            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1])
          }
        })
      }
    },
    onSelectAll: (event?: EditorEventInfo) => {
      if (event && event.content) {
        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
            let span = item as RichEditorTextSpanResult
            console.info('test onPaste' + span.value)
            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1])
          }
        })
      }
    }
  }

  @Builder sliderPanel() {
    Column() {
      Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
        Text('A').fontSize(15)
        Slider({ value: this.textSize, step: 10, style: SliderStyle.InSet })
          .width(210)
          .onChange((value: number, mode: SliderChangeMode) => {
            if (this.controller) {
              let selection = this.controller.getSelection();
              if (mode == SliderChangeMode.End) {
                if (this.textSize == undefined) {
                  this.textSize = 0
                }
                let spans = selection.spans
                spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
                  if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
                    this.textSize = Math.max(this.textSize, (item as RichEditorTextSpanResult).textStyle.fontSize)
                  }
                })
              }
              if (mode == SliderChangeMode.Moving || mode == SliderChangeMode.Click) {
                this.start = selection.selection[0]
                this.end = selection.selection[1]
                this.textSize = value
                this.controller.updateSpanStyle({
                  start: this.start,
                  end: this.end,
                  textStyle: { fontSize: this.textSize }
                })
              }
            }
          })
        Text('A').fontSize(20).fontWeight(FontWeight.Medium)
      }.borderRadius($r('sys.float.ohos_id_corner_radius_card'))
    }
    .shadow(ShadowStyle.OUTER_DEFAULT_MD)
    .backgroundColor(Color.White)
    .borderRadius($r('sys.float.ohos_id_corner_radius_card'))
    .padding(15)
    .height(48)
  }

  @Builder
  MyMenu() {
    Column() {
      SelectionMenu(this.selectionMenuOptions)
    }
    .width(256)
    .backgroundColor(Color.Transparent)
  }

  @Builder
  MyMenu2() {
    Column() {
      SelectionMenu({
        editorMenuOptions: this.editorMenuOptions,
        expandedMenuOptions: this.expandedMenuOptions1,
        controller: this.controller,
      })
    }
    .width(256)
    .backgroundColor(Color.Transparent)
  }

  @Builder
  MyMenu3() {
    Column() {
      SelectionMenu({
        editorMenuOptions: this.editorMenuOptions1,
        expandedMenuOptions: this.expandedMenuOptions,
        controller: this.controller,
      })
    }
    .width(256)
    .backgroundColor(Color.Transparent)
  }

  build() {
    Column() {
      Button("SetSelection")
        .onClick((event: ClickEvent) => {
          if (this.controller) {
            this.controller.setSelection(0, 2)
          }
        })

      RichEditor(this.options)
        .onReady(() => {
          this.controller.addTextSpan(this.message, { style: { fontColor: Color.Orange, fontSize: 30 } })
          this.controller.addTextSpan(this.message, { style: { fontColor: Color.Black, fontSize: 25 } })
        })
        .onSelect((value: RichEditorSelection) => {
          if (value.selection[0] == -1 && value.selection[1] == -1) {
            return
          }
          this.start = value.selection[0]
          this.end = value.selection[1]
        })
        .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu3(), RichEditorResponseType.RIGHT_CLICK)
        .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu2(), RichEditorResponseType.SELECT)
        .borderWidth(1)
        .borderColor(Color.Red)
        .width(200)
        .height(200)
    }
  }
}

说明

系统暂未预置加粗、斜体等图标,示例代码使用本地资源图标,开发者使用时需自行替换editorMenuOptions中icon项的资源。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐