• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# @ohos.arkui.advanced.SelectionMenu (文本选择菜单)
2
3
4文本选择菜单,适用于富文本组件通过[bindSelectionMenu](./ts-basic-components-richeditor.md#属性)绑定自定义文本选择菜单,建议绑定鼠标右键或者鼠标选中方式弹出,不支持作为普通组件单独使用。
5
6
7> **说明:**
8>
9> 该组件从API Version 11开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。
10
11
12## 导入模块
13
14```
15import { SelectionMenu, EditorMenuOptions, ExpandedMenuOptions, EditorEventInfo, SelectionMenuOptions } from '@ohos.arkui.advanced.SelectionMenu'
16```
17
18## 子组件
19
20无。
21
22## SelectionMenu
23
24SelectionMenu(options: SelectionMenuOptions)
25
26入参为空时,文本选择菜单组件SelectionMenu内容区大小及组件大小为零。表现例如,富文本组件[RichEditor](ts-basic-components-richeditor.md)使用[bindSelectionMenu](ts-basic-components-richeditor.md#属性)接口绑定一个SelectionMenu的右键菜单,则右键富文本组件区域时无任何菜单弹出。
27
28**装饰器类型:**\@Builder
29
30**系统能力:** SystemCapability.ArkUI.ArkUI.Full
31
32**参数:**
33
34| 名称 | 参数类型 | 必填 | 说明 |
35| -------- | -------- | -------- | -------- |
36| options | [SelectionMenuOptions](#selectionmenuoptions) | 是 | 文本选择菜单可选项。 |
37
38## SelectionMenuOptions
39
40SelectionMenuOptions({editorMenuOptions?: Array<EditorMenuOptions>, expandedMenuOptions?: Array<ExpandedMenuOptions>, controller?: RichEditorController, onCopy?: (event?: EditorEventInfo) => void, onPaste?: (event?: EditorEventInfo) => void, onCut?: (event?: EditorEventInfo) => void, onSelectAll?: (event?: EditorEventInfo) => void})
41
42SelectionMenuOptions定义SelectionMenu的可选菜单类型项及其具体配置参数。
43
44**系统能力:** SystemCapability.ArkUI.ArkUI.Full
45
46| 名称 | 参数类型 | 必填 | 说明 |
47| -------- | -------- | -------- | -------- |
48| editorMenuOptions | Array&lt;[EditorMenuOptions](#editormenuoptions)&gt; | 否 | 编辑菜单。<br/>editorMenuOptions未配置时,不显示编辑菜单。<br/>同时配置EditorMenuOptions中action和builder时,点击图标会同时响应。<br/>点击编辑菜单图标默认不关闭整个菜单,应用可以通过action接口配置RichEditorController的closeSelectionMenu主动关闭菜单。 |
49| expandedMenuOptions | Array&lt;[ExpandedMenuOptions](#expandedmenuoptions)&gt; | 否 | 扩展下拉菜单。<br/>expandedMenuOptions参数为空时无更多按钮,不显示扩展下拉菜单。<br/>expandedMenuOptions参数不为空时显示更多按钮,配置菜单项收起在更多按钮中,点击更多按钮展示。 |
50| controller | [RichEditorController](ts-basic-components-richeditor.md#richeditorcontroller) | 否 | 富文本控制器不为空时显示默认系统菜单(包含剪切复制粘贴等部分)且默认菜单功能内置。<br/>controller为空时不显示更多按钮,expandedMenuOptions参数不为空则显示下拉菜单中。<br/>系统默认只支持复制粘贴富文本文本内容,图文混排需要应用自定义onCopy、onPaste接口。应用自行配置onCopy \| onPaste接口时,系统菜单默认复制粘贴失效,调用应用自定义函数。 <br/>**说明:**<br/> 点击自定义文本选择菜单内置复制功能选项后,自定义菜单消失选中文本高亮保留。<br/> 点击自定义文本选择菜单内置全选功能选项后,自定义菜单消失文本全选高亮。<br/> 点击自定义文本选择菜单内置粘贴功能选项后,空白处粘贴或者选中文本替换粘贴均是保留被复制文本的样式。<br/> 当富文本组件[RichEditor](ts-basic-components-richeditor.md)的copyOptions属性设置为`CopyOptions.None`时,内置的复制剪切功能不会被限制。|
51| onCopy | (event?: [EditorEventInfo](#editoreventinfo))&nbsp;=&gt;&nbsp;void | 否 | 替代内置系统菜单复制项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置复制功能。<br/>**说明:**<br/> event为返回信息。|
52| onPaste | (event?: [EditorEventInfo](#editoreventinfo))&nbsp;=&gt;&nbsp;void | 否 | 替代内置系统菜单粘贴项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置粘贴功能。<br/>**说明:**<br/> event为返回信息。 |
53| onCut | (event?: [EditorEventInfo](#editoreventinfo))&nbsp;=&gt;&nbsp;void | 否 | 替代内置系统菜单剪切项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置剪切功能。<br/>**说明:**<br/>event为返回信息。|
54| onSelectAll | (event?: [EditorEventInfo](#editoreventinfo))&nbsp;=&gt;&nbsp;void | 否 | 替代内置系统菜单全选项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置全选功能。<br/>**说明:**<br/>event为返回信息。|
55
56
57## EditorMenuOptions
58
59编辑菜单选项。
60
61**系统能力:** SystemCapability.ArkUI.ArkUI.Full
62
63| 名称 | 类型 | 必填 | 说明 |
64| -------- | -------- | -------- | -------- |
65| icon | [ResourceStr](ts-types.md#resourcestr) | 是 | 图标资源。 |
66| builder | ()&nbsp;=&gt;&nbsp;void | 否 | 点击时显示用户自定义组件,自定义组件在构造时结合@Builder使用。 |
67| action | ()&nbsp;=&gt;&nbsp;void | 否 | 点击菜单项的事件回调。 |
68
69
70## ExpandedMenuOptions
71
72扩展下拉菜单。
73
74继承于[MenuItemOptions](ts-basic-components-menuitem.md#menuitemoptions类型说明)
75
76**系统能力:** SystemCapability.ArkUI.ArkUI.Full
77
78| 名称 | 类型 | 必填 | 说明 |
79| -------- | -------- | -------- | -------- |
80| action | ()&nbsp;=&gt;&nbsp;void | 否 | 点击菜单项的事件回调。 |
81
82## EditorEventInfo
83
84选中内容信息。
85
86**系统能力:** SystemCapability.ArkUI.ArkUI.Full
87
88| 名称 | 类型 | 必填 | 说明 |
89| -------- | -------- | -------- | -------- |
90| content | [RichEditorSelection](ts-basic-components-richeditor.md#richeditorselection) | 否 | 选中内容信息。|
91
92## 属性
93
94不支持[通用属性](ts-universal-attributes-size.md),宽度默认256vp, 高度自适应内容。
95
96## 事件
97不支持[通用事件](ts-universal-events-click.md)。
98
99## 示例
100
101```ts
102import { SelectionMenu, EditorMenuOptions, ExpandedMenuOptions, EditorEventInfo, SelectionMenuOptions } from '@ohos.arkui.advanced.SelectionMenu'
103
104@Entry
105@Component
106struct Index {
107  @State select: boolean = true
108  controller: RichEditorController = new RichEditorController();
109  options: RichEditorOptions = { controller: this.controller }
110  @State message: string = 'Hello word'
111  @State textSize: number = 30
112  @State fontWeight: FontWeight = FontWeight.Normal
113  @State start: number = -1
114  @State end: number = -1
115  @State visibleValue: Visibility = Visibility.Visible
116  @State colorTransparent: Color = Color.Transparent
117  @State textStyle: RichEditorTextStyle = {}
118  private editorMenuOptions: Array<EditorMenuOptions> =
119    [
120      { icon: $r("app.media.ic_notepad_textbold"), action: () => {
121        if (this.controller) {
122          let selection = this.controller.getSelection();
123          let spans = selection.spans
124          spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
125            if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
126              let span = item as RichEditorTextSpanResult
127              this.textStyle = span.textStyle
128              let start = span.offsetInSpan[0]
129              let end = span.offsetInSpan[1]
130              let offset = span.spanPosition.spanRange[0]
131              if (this.textStyle.fontWeight != 11) {
132                this.textStyle.fontWeight = FontWeight.Bolder
133              } else {
134                this.textStyle.fontWeight = FontWeight.Normal
135              }
136              this.controller.updateSpanStyle({
137                start: offset + start,
138                end: offset + end,
139                textStyle: this.textStyle
140              })
141            }
142          })
143        }
144      } },
145      { icon: $r("app.media.ic_notepad_texttilt"), action: () => {
146        if (this.controller) {
147          let selection = this.controller.getSelection();
148          let spans = selection.spans
149          spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
150            if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
151              let span = item as RichEditorTextSpanResult
152              this.textStyle = span.textStyle
153              let start = span.offsetInSpan[0]
154              let end = span.offsetInSpan[1]
155              let offset = span.spanPosition.spanRange[0]
156              if (this.textStyle.fontStyle == FontStyle.Italic) {
157                this.textStyle.fontStyle = FontStyle.Normal
158              } else {
159                this.textStyle.fontStyle = FontStyle.Italic
160              }
161              this.controller.updateSpanStyle({
162                start: offset + start,
163                end: offset + end,
164                textStyle: this.textStyle
165              })
166            }
167          })
168        }
169      } },
170      { icon: $r("app.media.ic_notepad_underline"),
171        action: () => {
172          if (this.controller) {
173            let selection = this.controller.getSelection();
174            let spans = selection.spans
175            spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
176              if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
177                let span = item as RichEditorTextSpanResult
178                this.textStyle = span.textStyle
179                let start = span.offsetInSpan[0]
180                let end = span.offsetInSpan[1]
181                let offset = span.spanPosition.spanRange[0]
182                if (this.textStyle.decoration) {
183                  if (this.textStyle.decoration.type == TextDecorationType.Underline) {
184                    this.textStyle.decoration.type = TextDecorationType.None
185                  } else {
186                    this.textStyle.decoration.type = TextDecorationType.Underline
187                  }
188                } else {
189                  this.textStyle.decoration = { type: TextDecorationType.Underline, color: Color.Black }
190                }
191                this.controller.updateSpanStyle({
192                  start: offset + start,
193                  end: offset + end,
194                  textStyle: this.textStyle
195                })
196              }
197            })
198          }
199        }
200      },
201      { icon: $r("app.media.app_icon"), action: () => {
202      }, builder: (): void => this.sliderPanel() },
203      { icon: $r("app.media.ic_notepad_textcolor"), action: () => {
204        if (this.controller) {
205          let selection = this.controller.getSelection();
206          let spans = selection.spans
207          spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
208            if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
209              let span = item as RichEditorTextSpanResult
210              this.textStyle = span.textStyle
211              let start = span.offsetInSpan[0]
212              let end = span.offsetInSpan[1]
213              let offset = span.spanPosition.spanRange[0]
214              if (this.textStyle.fontColor == Color.Orange || this.textStyle.fontColor == '#FFFFA500') {
215                this.textStyle.fontColor = Color.Black
216              } else {
217                this.textStyle.fontColor = Color.Orange
218              }
219              this.controller.updateSpanStyle({
220                start: offset + start,
221                end: offset + end,
222                textStyle: this.textStyle
223              })
224            }
225          })
226        }
227      } }]
228  private expandedMenuOptions: Array<ExpandedMenuOptions> =
229    [{ startIcon: $r("app.media.icon"), content: '词典', action: () => {
230    } }, { startIcon: $r("app.media.icon"), content: '翻译', action: () => {
231    } }, { startIcon: $r("app.media.icon"), content: '搜索', action: () => {
232    } }]
233  private expandedMenuOptions1: Array<ExpandedMenuOptions> = []
234  private editorMenuOptions1: Array<EditorMenuOptions> = []
235  private selectionMenuOptions: SelectionMenuOptions = {
236    editorMenuOptions: this.editorMenuOptions,
237    expandedMenuOptions: this.expandedMenuOptions,
238    controller: this.controller,
239    onCut: (event?: EditorEventInfo) => {
240      if (event && event.content) {
241        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
242          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
243            let span = item as RichEditorTextSpanResult
244            console.info('test cut' + span.value)
245            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1])
246          }
247        })
248      }
249    },
250    onPaste: (event?: EditorEventInfo) => {
251      if (event && event.content) {
252        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
253          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
254            let span = item as RichEditorTextSpanResult
255            console.info('test onPaste' + span.value)
256            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1])
257          }
258        })
259      }
260    },
261    onCopy: (event?: EditorEventInfo) => {
262      if (event && event.content) {
263        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
264          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
265            let span = item as RichEditorTextSpanResult
266            console.info('test cut' + span.value)
267            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1])
268          }
269        })
270      }
271    },
272    onSelectAll: (event?: EditorEventInfo) => {
273      if (event && event.content) {
274        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
275          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
276            let span = item as RichEditorTextSpanResult
277            console.info('test onPaste' + span.value)
278            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1])
279          }
280        })
281      }
282    }
283  }
284
285  @Builder sliderPanel() {
286    Column() {
287      Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
288        Text('A').fontSize(15)
289        Slider({ value: this.textSize, step: 10, style: SliderStyle.InSet })
290          .width(210)
291          .onChange((value: number, mode: SliderChangeMode) => {
292            if (this.controller) {
293              let selection = this.controller.getSelection();
294              if (mode == SliderChangeMode.End) {
295                if (this.textSize == undefined) {
296                  this.textSize = 0
297                }
298                let spans = selection.spans
299                spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
300                  if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
301                    this.textSize = Math.max(this.textSize, (item as RichEditorTextSpanResult).textStyle.fontSize)
302                  }
303                })
304              }
305              if (mode == SliderChangeMode.Moving || mode == SliderChangeMode.Click) {
306                this.start = selection.selection[0]
307                this.end = selection.selection[1]
308                this.textSize = value
309                this.controller.updateSpanStyle({
310                  start: this.start,
311                  end: this.end,
312                  textStyle: { fontSize: this.textSize }
313                })
314              }
315            }
316          })
317        Text('A').fontSize(20).fontWeight(FontWeight.Medium)
318      }.borderRadius($r('sys.float.ohos_id_corner_radius_card'))
319    }
320    .shadow(ShadowStyle.OUTER_DEFAULT_MD)
321    .backgroundColor(Color.White)
322    .borderRadius($r('sys.float.ohos_id_corner_radius_card'))
323    .padding(15)
324    .height(48)
325  }
326
327  @Builder
328  MyMenu() {
329    Column() {
330      SelectionMenu(this.selectionMenuOptions)
331    }
332    .width(256)
333    .backgroundColor(Color.Transparent)
334  }
335
336  @Builder
337  MyMenu2() {
338    Column() {
339      SelectionMenu({
340        editorMenuOptions: this.editorMenuOptions,
341        expandedMenuOptions: this.expandedMenuOptions1,
342        controller: this.controller,
343      })
344    }
345    .width(256)
346    .backgroundColor(Color.Transparent)
347  }
348
349  @Builder
350  MyMenu3() {
351    Column() {
352      SelectionMenu({
353        editorMenuOptions: this.editorMenuOptions1,
354        expandedMenuOptions: this.expandedMenuOptions,
355        controller: this.controller,
356      })
357    }
358    .width(256)
359    .backgroundColor(Color.Transparent)
360  }
361
362  build() {
363    Column() {
364      Button("SetSelection")
365        .onClick((event: ClickEvent) => {
366          if (this.controller) {
367            this.controller.setSelection(0, 2)
368          }
369        })
370
371      RichEditor(this.options)
372        .onReady(() => {
373          this.controller.addTextSpan(this.message, { style: { fontColor: Color.Orange, fontSize: 30 } })
374          this.controller.addTextSpan(this.message, { style: { fontColor: Color.Black, fontSize: 25 } })
375        })
376        .onSelect((value: RichEditorSelection) => {
377          if (value.selection[0] == -1 && value.selection[1] == -1) {
378            return
379          }
380          this.start = value.selection[0]
381          this.end = value.selection[1]
382        })
383        .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu3(), RichEditorResponseType.RIGHT_CLICK)
384        .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu2(), RichEditorResponseType.SELECT)
385        .borderWidth(1)
386        .borderColor(Color.Red)
387        .width(200)
388        .height(200)
389    }
390  }
391}
392```
393> **说明:**
394>
395> 系统暂未预置加粗、斜体等图标,示例代码使用本地资源图标,开发者使用时需自行替换editorMenuOptions中icon项的资源。
396
397![selectionmenu](figures/selectionmenu.jpeg)
398