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<[EditorMenuOptions](#editormenuoptions)> | 否 | 编辑菜单。<br/>editorMenuOptions未配置时,不显示编辑菜单。<br/>同时配置EditorMenuOptions中action和builder时,点击图标会同时响应。<br/>点击编辑菜单图标默认不关闭整个菜单,应用可以通过action接口配置RichEditorController的closeSelectionMenu主动关闭菜单。 | 49| expandedMenuOptions | Array<[ExpandedMenuOptions](#expandedmenuoptions)> | 否 | 扩展下拉菜单。<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)) => void | 否 | 替代内置系统菜单复制项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置复制功能。<br/>**说明:**<br/> event为返回信息。| 52| onPaste | (event?: [EditorEventInfo](#editoreventinfo)) => void | 否 | 替代内置系统菜单粘贴项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置粘贴功能。<br/>**说明:**<br/> event为返回信息。 | 53| onCut | (event?: [EditorEventInfo](#editoreventinfo)) => void | 否 | 替代内置系统菜单剪切项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置剪切功能。<br/>**说明:**<br/>event为返回信息。| 54| onSelectAll | (event?: [EditorEventInfo](#editoreventinfo)) => 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 | () => void | 否 | 点击时显示用户自定义组件,自定义组件在构造时结合@Builder使用。 | 67| action | () => 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 | () => 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 398