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