1# SelectionMenu 2 3 4文本选择菜单,适用于[RichEditor](ts-basic-components-richeditor.md)组件通过[bindSelectionMenu](ts-basic-components-richeditor.md#bindselectionmenu)或[Text](ts-basic-components-text.md)组件通过[bindSelectionMenu](ts-basic-components-text.md#bindselectionmenu11)绑定自定义文本选择菜单,建议绑定鼠标右键或者鼠标选中方式弹出,不支持作为普通组件单独使用。 5 6 7> **说明:** 8> 9> 该组件从API Version 11开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。 10> 11> 该组件不支持在Wearable设备上使用。 12 13 14## 导入模块 15 16``` 17import { SelectionMenu, EditorMenuOptions, ExpandedMenuOptions, EditorEventInfo, SelectionMenuOptions } from '@kit.ArkUI' 18``` 19 20## 子组件 21 22无。 23 24## SelectionMenu 25 26SelectionMenu(options: SelectionMenuOptions): void 27 28入参为空时,文本选择菜单组件SelectionMenu内容区大小及组件大小为零。表现例如,富文本组件[RichEditor](ts-basic-components-richeditor.md)使用[bindSelectionMenu](ts-basic-components-richeditor.md#bindselectionmenu)接口绑定一个SelectionMenu的右键菜单,则右键富文本组件区域时无任何菜单弹出。 29 30**装饰器类型:**\@Builder 31 32**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 33 34**系统能力:** SystemCapability.ArkUI.ArkUI.Full 35 36**参数:** 37 38| 参数名 | 类型 | 必填 | 说明 | 39| -------- | -------- | -------- | -------- | 40| options | [SelectionMenuOptions](#selectionmenuoptions) | 是 | 文本选择菜单可选项。 | 41 42## SelectionMenuOptions 43 44SelectionMenuOptions定义SelectionMenu的可选菜单类型项及其具体配置参数。 45 46**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 47 48**系统能力:** SystemCapability.ArkUI.ArkUI.Full 49 50| 名称 | 类型 | 必填 | 说明 | 51| -------- | -------- | -------- | -------- | 52| editorMenuOptions | Array<[EditorMenuOptions](#editormenuoptions)> | 否 | 编辑菜单。<br/>editorMenuOptions未配置时,不显示编辑菜单。<br/>同时配置EditorMenuOptions中action和builder时,点击图标会同时响应。<br/>点击编辑菜单图标默认不关闭整个菜单,应用可以通过action接口配置RichEditorController的closeSelectionMenu主动关闭菜单。 | 53| expandedMenuOptions | Array<[ExpandedMenuOptions](#expandedmenuoptions)> | 否 | 扩展下拉菜单。<br/>expandedMenuOptions参数为空时无更多按钮,不显示扩展下拉菜单。<br/>expandedMenuOptions参数不为空时显示更多按钮,配置菜单项收起在更多按钮中,点击更多按钮展示。 | 54| 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`时,内置的复制剪切功能不会被限制。| 55| onCopy | (event?: [EditorEventInfo](#editoreventinfo)) => void | 否 | 替代内置系统菜单复制项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置复制功能。<br/>**说明:**<br/> event为返回信息。| 56| onPaste | (event?: [EditorEventInfo](#editoreventinfo)) => void | 否 | 替代内置系统菜单粘贴项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置粘贴功能。<br/>**说明:**<br/> event为返回信息。 | 57| onCut | (event?: [EditorEventInfo](#editoreventinfo)) => void | 否 | 替代内置系统菜单剪切项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置剪切功能。<br/>**说明:**<br/>event为返回信息。| 58| onSelectAll | (event?: [EditorEventInfo](#editoreventinfo)) => void | 否 | 替代内置系统菜单全选项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置全选功能。<br/>**说明:**<br/>event为返回信息。| 59 60 61## EditorMenuOptions 62 63编辑菜单选项。 64 65**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 66 67**系统能力:** SystemCapability.ArkUI.ArkUI.Full 68 69| 名称 | 类型 | 必填 | 说明 | 70| -------- | -------- | -------- | -------- | 71| icon | [ResourceStr](ts-types.md#resourcestr) | 是 | 图标资源。 | 72| symbolStyle<sup>18+</sup> | [SymbolGlyphModifier](ts-universal-attributes-attribute-modifier.md) | 否 | Symbol图标资源,优先级大于icon。<br/>**原子化服务API:** 从API version 18开始,该接口支持在原子化服务中使用。 | 73| builder | () => void | 否 | 点击时显示用户自定义组件,自定义组件在构造时结合@Builder使用。 | 74| action | () => void | 否 | 点击菜单项的事件回调。 | 75 76 77## ExpandedMenuOptions 78 79扩展下拉菜单。 80 81继承于[MenuItemOptions](ts-basic-components-menuitem.md#menuitemoptions对象说明)。 82 83**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 84 85**系统能力:** SystemCapability.ArkUI.ArkUI.Full 86 87| 名称 | 类型 | 必填 | 说明 | 88| -------- | -------- | -------- | -------- | 89| action | () => void | 否 | 点击菜单项的事件回调。 | 90 91## EditorEventInfo 92 93选中内容信息。 94 95**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 96 97**系统能力:** SystemCapability.ArkUI.ArkUI.Full 98 99| 名称 | 类型 | 必填 | 说明 | 100| -------- | -------- | -------- | -------- | 101| content | [RichEditorSelection](ts-basic-components-richeditor.md#richeditorselection) | 否 | 选中内容信息。| 102 103## 属性 104 105不支持[通用属性](ts-component-general-attributes.md),宽度默认224vp, 高度自适应内容。 106 107## 事件 108不支持[通用事件](ts-component-general-events.md)。 109 110## 示例 111### 示例1(绑定不同触发方式的自定义文本选择菜单) 112 113该示例展示了文本绑定不同触发方式的自定义文本选择菜单的效果。 114 115```ts 116import { 117 SelectionMenu, 118 EditorMenuOptions, 119 ExpandedMenuOptions, 120 EditorEventInfo, 121 SelectionMenuOptions 122} from '@kit.ArkUI' 123 124@Entry 125@Component 126struct Index { 127 @State select: boolean = true; 128 controller: RichEditorController = new RichEditorController(); 129 options: RichEditorOptions = { controller: this.controller }; 130 @State message: string = 'Hello world'; 131 @State textSize: number = 30; 132 @State fontWeight: FontWeight = FontWeight.Normal; 133 @State start: number = -1; 134 @State end: number = -1; 135 @State visibleValue: Visibility = Visibility.Visible; 136 @State colorTransparent: Color = Color.Transparent; 137 @State textStyle: RichEditorTextStyle = {}; 138 private editorMenuOptions: Array<EditorMenuOptions> = 139 [ 140 { 141 icon: $r("app.media.ic_notepad_textbold"), action: () => { 142 if (this.controller) { 143 let selection = this.controller.getSelection(); 144 let spans = selection.spans; 145 spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 146 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 147 let span = item as RichEditorTextSpanResult; 148 this.textStyle = span.textStyle; 149 let start = span.offsetInSpan[0]; 150 let end = span.offsetInSpan[1]; 151 let offset = span.spanPosition.spanRange[0]; 152 if (this.textStyle.fontWeight != 11) { 153 this.textStyle.fontWeight = FontWeight.Bolder; 154 } else { 155 this.textStyle.fontWeight = FontWeight.Normal; 156 } 157 this.controller.updateSpanStyle({ 158 start: offset + start, 159 end: offset + end, 160 textStyle: this.textStyle 161 }) 162 } 163 }) 164 } 165 } 166 }, 167 { 168 icon: $r("app.media.ic_notepad_texttilt"), action: () => { 169 if (this.controller) { 170 let selection = this.controller.getSelection(); 171 let spans = selection.spans; 172 spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 173 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 174 let span = item as RichEditorTextSpanResult; 175 this.textStyle = span.textStyle; 176 let start = span.offsetInSpan[0]; 177 let end = span.offsetInSpan[1]; 178 let offset = span.spanPosition.spanRange[0]; 179 if (this.textStyle.fontStyle == FontStyle.Italic) { 180 this.textStyle.fontStyle = FontStyle.Normal; 181 } else { 182 this.textStyle.fontStyle = FontStyle.Italic; 183 } 184 this.controller.updateSpanStyle({ 185 start: offset + start, 186 end: offset + end, 187 textStyle: this.textStyle 188 }) 189 } 190 }) 191 } 192 } 193 }, 194 { 195 icon: $r("app.media.ic_notepad_underline"), 196 action: () => { 197 if (this.controller) { 198 let selection = this.controller.getSelection(); 199 let spans = selection.spans; 200 spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 201 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 202 let span = item as RichEditorTextSpanResult; 203 this.textStyle = span.textStyle; 204 let start = span.offsetInSpan[0]; 205 let end = span.offsetInSpan[1]; 206 let offset = span.spanPosition.spanRange[0]; 207 if (this.textStyle.decoration) { 208 if (this.textStyle.decoration.type == TextDecorationType.Underline) { 209 this.textStyle.decoration.type = TextDecorationType.None; 210 } else { 211 this.textStyle.decoration.type = TextDecorationType.Underline; 212 } 213 } else { 214 this.textStyle.decoration = { type: TextDecorationType.Underline, color: Color.Black } 215 } 216 this.controller.updateSpanStyle({ 217 start: offset + start, 218 end: offset + end, 219 textStyle: this.textStyle 220 }) 221 } 222 }) 223 } 224 } 225 }, 226 { 227 icon: $r("app.media.ic_notepad_fontsize"), action: () => { 228 }, builder: (): void => this.sliderPanel() 229 }, 230 { 231 icon: $r("app.media.ic_notepad_textcolor"), action: () => { 232 if (this.controller) { 233 let selection = this.controller.getSelection(); 234 let spans = selection.spans; 235 spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 236 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 237 let span = item as RichEditorTextSpanResult; 238 this.textStyle = span.textStyle; 239 let start = span.offsetInSpan[0]; 240 let end = span.offsetInSpan[1]; 241 let offset = span.spanPosition.spanRange[0]; 242 if (this.textStyle.fontColor == Color.Orange || this.textStyle.fontColor == '#FFFFA500') { 243 this.textStyle.fontColor = Color.Black; 244 } else { 245 this.textStyle.fontColor = Color.Orange; 246 } 247 this.controller.updateSpanStyle({ 248 start: offset + start, 249 end: offset + end, 250 textStyle: this.textStyle 251 }) 252 } 253 }) 254 } 255 } 256 }] 257 private expandedMenuOptions: Array<ExpandedMenuOptions> = 258 [{ 259 startIcon: $r("app.media.startIcon"), content: '词典', action: () => { 260 } 261 }, { 262 startIcon: $r("app.media.startIcon"), content: '翻译', action: () => { 263 } 264 }, { 265 startIcon: $r("app.media.startIcon"), content: '搜索', action: () => { 266 } 267 }] 268 private expandedMenuOptions1: Array<ExpandedMenuOptions> = [] 269 private editorMenuOptions1: Array<EditorMenuOptions> = [] 270 private selectionMenuOptions: SelectionMenuOptions = { 271 editorMenuOptions: this.editorMenuOptions, 272 expandedMenuOptions: this.expandedMenuOptions, 273 controller: this.controller, 274 onCut: (event?: EditorEventInfo) => { 275 if (event && event.content) { 276 event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 277 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 278 let span = item as RichEditorTextSpanResult; 279 console.info('test cut' + span.value); 280 console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]); 281 } 282 }) 283 } 284 }, 285 onPaste: (event?: EditorEventInfo) => { 286 if (event && event.content) { 287 event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 288 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 289 let span = item as RichEditorTextSpanResult; 290 console.info('test onPaste' + span.value); 291 console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]); 292 } 293 }) 294 } 295 }, 296 onCopy: (event?: EditorEventInfo) => { 297 if (event && event.content) { 298 event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 299 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 300 let span = item as RichEditorTextSpanResult; 301 console.info('test cut' + span.value); 302 console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]); 303 } 304 }) 305 } 306 }, 307 onSelectAll: (event?: EditorEventInfo) => { 308 if (event && event.content) { 309 event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 310 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 311 let span = item as RichEditorTextSpanResult; 312 console.info('test onPaste' + span.value); 313 console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]); 314 } 315 }) 316 } 317 } 318 } 319 320 @Builder 321 sliderPanel() { 322 Column() { 323 Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { 324 Text('A').fontSize(15) 325 Slider({ value: this.textSize, step: 10, style: SliderStyle.InSet }) 326 .width(210) 327 .onChange((value: number, mode: SliderChangeMode) => { 328 if (this.controller) { 329 let selection = this.controller.getSelection(); 330 if (mode == SliderChangeMode.End) { 331 if (this.textSize == undefined) { 332 this.textSize = 0; 333 } 334 let spans = selection.spans; 335 spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 336 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 337 this.textSize = Math.max(this.textSize, (item as RichEditorTextSpanResult).textStyle.fontSize); 338 } 339 }) 340 } 341 if (mode == SliderChangeMode.Moving || mode == SliderChangeMode.Click) { 342 this.start = selection.selection[0]; 343 this.end = selection.selection[1]; 344 this.textSize = value; 345 this.controller.updateSpanStyle({ 346 start: this.start, 347 end: this.end, 348 textStyle: { fontSize: this.textSize } 349 }) 350 } 351 } 352 }) 353 Text('A').fontSize(20).fontWeight(FontWeight.Medium) 354 }.borderRadius($r('sys.float.ohos_id_corner_radius_card')) 355 } 356 .shadow(ShadowStyle.OUTER_DEFAULT_MD) 357 .backgroundColor(Color.White) 358 .borderRadius($r('sys.float.ohos_id_corner_radius_card')) 359 .padding(15) 360 .height(48) 361 } 362 363 @Builder 364 MyMenu() { 365 Column() { 366 SelectionMenu(this.selectionMenuOptions) 367 } 368 .width(256) 369 .backgroundColor(Color.Transparent) 370 } 371 372 @Builder 373 MyMenu2() { 374 Column() { 375 SelectionMenu({ 376 editorMenuOptions: this.editorMenuOptions, 377 expandedMenuOptions: this.expandedMenuOptions1, 378 controller: this.controller, 379 }) 380 } 381 .width(256) 382 .backgroundColor(Color.Transparent) 383 } 384 385 @Builder 386 MyMenu3() { 387 Column() { 388 SelectionMenu({ 389 editorMenuOptions: this.editorMenuOptions, 390 expandedMenuOptions: this.expandedMenuOptions, 391 controller: this.controller, 392 }) 393 } 394 .width(256) 395 .backgroundColor(Color.Transparent) 396 } 397 398 build() { 399 Column() { 400 Button("SetSelection") 401 .onClick((event: ClickEvent) => { 402 if (this.controller) { 403 this.controller.setSelection(0, 2); 404 } 405 }) 406 407 RichEditor(this.options) 408 .onReady(() => { 409 this.controller.addTextSpan(this.message, { style: { fontColor: Color.Orange, fontSize: 30 } }); 410 this.controller.addTextSpan(this.message, { style: { fontColor: Color.Black, fontSize: 25 } }); 411 }) 412 .onSelect((value: RichEditorSelection) => { 413 if (value.selection[0] == -1 && value.selection[1] == -1) { 414 return; 415 } 416 this.start = value.selection[0]; 417 this.end = value.selection[1]; 418 }) 419 .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu3(), RichEditorResponseType.RIGHT_CLICK) 420 .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu2(), RichEditorResponseType.SELECT) 421 .borderWidth(1) 422 .borderColor(Color.Red) 423 .width(200) 424 .height(200) 425 .margin(10) 426 } 427 } 428} 429``` 430> **说明:** 431> 432> 系统暂未预置加粗、斜体等图标,示例代码使用本地资源图标,开发者使用时需自行替换editorMenuOptions中icon项的资源。 433 434 435 436### 示例2(设置Symbol类型图标) 437 438该示例通过设置EditorMenuOptions的属性symbolStyle,展示了自定义Symbol类型图标。 439 440```ts 441import { 442 SelectionMenu, 443 EditorMenuOptions, 444 ExpandedMenuOptions, 445 EditorEventInfo, 446 SelectionMenuOptions, 447 SymbolGlyphModifier 448} from '@kit.ArkUI' 449 450@Entry 451@Component 452struct Index { 453 @State select: boolean = true; 454 controller: RichEditorController = new RichEditorController(); 455 options: RichEditorOptions = { controller: this.controller }; 456 @State message: string = 'Hello world'; 457 @State textSize: number = 30; 458 @State fontWeight: FontWeight = FontWeight.Normal; 459 @State start: number = -1; 460 @State end: number = -1; 461 @State visibleValue: Visibility = Visibility.Visible; 462 @State colorTransparent: Color = Color.Transparent; 463 @State textStyle: RichEditorTextStyle = {}; 464 private editorMenuOptions: Array<EditorMenuOptions> = 465 [ 466 { 467 icon: $r("sys.media.wifi_router_fill"), 468 symbolStyle: new SymbolGlyphModifier($r('sys.symbol.save')), 469 action: () => { 470 if (this.controller) { 471 let selection = this.controller.getSelection(); 472 let spans = selection.spans; 473 spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 474 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 475 let span = item as RichEditorTextSpanResult; 476 this.textStyle = span.textStyle; 477 let start = span.offsetInSpan[0]; 478 let end = span.offsetInSpan[1]; 479 let offset = span.spanPosition.spanRange[0]; 480 if (this.textStyle.fontWeight != 11) { 481 this.textStyle.fontWeight = FontWeight.Bolder; 482 } else { 483 this.textStyle.fontWeight = FontWeight.Normal; 484 } 485 this.controller.updateSpanStyle({ 486 start: offset + start, 487 end: offset + end, 488 textStyle: this.textStyle 489 }) 490 } 491 }) 492 } 493 } 494 }, 495 { 496 icon: $r("sys.media.save_button_picture"), 497 symbolStyle: new SymbolGlyphModifier($r('sys.symbol.camera')), 498 action: () => { 499 if (this.controller) { 500 let selection = this.controller.getSelection(); 501 let spans = selection.spans; 502 spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 503 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 504 let span = item as RichEditorTextSpanResult; 505 this.textStyle = span.textStyle; 506 let start = span.offsetInSpan[0]; 507 let end = span.offsetInSpan[1]; 508 let offset = span.spanPosition.spanRange[0]; 509 if (this.textStyle.fontStyle == FontStyle.Italic) { 510 this.textStyle.fontStyle = FontStyle.Normal; 511 } else { 512 this.textStyle.fontStyle = FontStyle.Italic; 513 } 514 this.controller.updateSpanStyle({ 515 start: offset + start, 516 end: offset + end, 517 textStyle: this.textStyle 518 }) 519 } 520 }) 521 } 522 } 523 }, 524 { 525 icon: $r("sys.media.waveform_folder_fill"), 526 symbolStyle: new SymbolGlyphModifier($r('sys.symbol.car')), 527 action: () => { 528 if (this.controller) { 529 let selection = this.controller.getSelection(); 530 let spans = selection.spans; 531 spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 532 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 533 let span = item as RichEditorTextSpanResult; 534 this.textStyle = span.textStyle; 535 let start = span.offsetInSpan[0]; 536 let end = span.offsetInSpan[1]; 537 let offset = span.spanPosition.spanRange[0]; 538 if (this.textStyle.decoration) { 539 if (this.textStyle.decoration.type == TextDecorationType.Underline) { 540 this.textStyle.decoration.type = TextDecorationType.None; 541 } else { 542 this.textStyle.decoration.type = TextDecorationType.Underline; 543 } 544 } else { 545 this.textStyle.decoration = { type: TextDecorationType.Underline, color: Color.Black } 546 } 547 this.controller.updateSpanStyle({ 548 start: offset + start, 549 end: offset + end, 550 textStyle: this.textStyle 551 }) 552 } 553 }) 554 } 555 } 556 }, 557 { 558 icon: $r("app.media.app_icon"), action: () => { 559 }, builder: (): void => this.sliderPanel() 560 }, 561 { 562 icon: $r("sys.media.thermometer_fill"), action: () => { 563 if (this.controller) { 564 let selection = this.controller.getSelection(); 565 let spans = selection.spans; 566 spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 567 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 568 let span = item as RichEditorTextSpanResult; 569 this.textStyle = span.textStyle; 570 let start = span.offsetInSpan[0]; 571 let end = span.offsetInSpan[1]; 572 let offset = span.spanPosition.spanRange[0]; 573 if (this.textStyle.fontColor == Color.Orange || this.textStyle.fontColor == '#FFFFA500') { 574 this.textStyle.fontColor = Color.Black; 575 } else { 576 this.textStyle.fontColor = Color.Orange; 577 } 578 this.controller.updateSpanStyle({ 579 start: offset + start, 580 end: offset + end, 581 textStyle: this.textStyle 582 }) 583 } 584 }) 585 } 586 } 587 }] 588 private expandedMenuOptions: Array<ExpandedMenuOptions> = 589 [{ 590 startIcon: $r("app.media.startIcon"), content: '词典', action: () => { 591 } 592 }, { 593 startIcon: $r("app.media.startIcon"), content: '翻译', action: () => { 594 } 595 }, { 596 startIcon: $r("app.media.startIcon"), content: '搜索', action: () => { 597 } 598 }] 599 private expandedMenuOptions1: Array<ExpandedMenuOptions> = [] 600 private editorMenuOptions1: Array<EditorMenuOptions> = [] 601 private selectionMenuOptions: SelectionMenuOptions = { 602 editorMenuOptions: this.editorMenuOptions, 603 expandedMenuOptions: this.expandedMenuOptions, 604 controller: this.controller, 605 onCut: (event?: EditorEventInfo) => { 606 if (event && event.content) { 607 event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 608 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 609 let span = item as RichEditorTextSpanResult; 610 console.info('test cut' + span.value); 611 console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]); 612 } 613 }) 614 } 615 }, 616 onPaste: (event?: EditorEventInfo) => { 617 if (event && event.content) { 618 event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 619 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 620 let span = item as RichEditorTextSpanResult; 621 console.info('test onPaste' + span.value); 622 console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]); 623 } 624 }) 625 } 626 }, 627 onCopy: (event?: EditorEventInfo) => { 628 if (event && event.content) { 629 event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 630 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 631 let span = item as RichEditorTextSpanResult; 632 console.info('test cut' + span.value); 633 console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]); 634 } 635 }) 636 } 637 }, 638 onSelectAll: (event?: EditorEventInfo) => { 639 if (event && event.content) { 640 event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 641 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 642 let span = item as RichEditorTextSpanResult; 643 console.info('test onPaste' + span.value); 644 console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]); 645 } 646 }) 647 } 648 } 649 } 650 651 @Builder 652 sliderPanel() { 653 Column() { 654 Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { 655 Text('A').fontSize(15) 656 Slider({ value: this.textSize, step: 10, style: SliderStyle.InSet }) 657 .width(210) 658 .onChange((value: number, mode: SliderChangeMode) => { 659 if (this.controller) { 660 let selection = this.controller.getSelection(); 661 if (mode == SliderChangeMode.End) { 662 if (this.textSize == undefined) { 663 this.textSize = 0; 664 } 665 let spans = selection.spans; 666 spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 667 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 668 this.textSize = Math.max(this.textSize, (item as RichEditorTextSpanResult).textStyle.fontSize); 669 } 670 }) 671 } 672 if (mode == SliderChangeMode.Moving || mode == SliderChangeMode.Click) { 673 this.start = selection.selection[0]; 674 this.end = selection.selection[1]; 675 this.textSize = value; 676 this.controller.updateSpanStyle({ 677 start: this.start, 678 end: this.end, 679 textStyle: { fontSize: this.textSize } 680 }) 681 } 682 } 683 }) 684 Text('A').fontSize(20).fontWeight(FontWeight.Medium) 685 }.borderRadius($r('sys.float.ohos_id_corner_radius_card')) 686 } 687 .shadow(ShadowStyle.OUTER_DEFAULT_MD) 688 .backgroundColor(Color.White) 689 .borderRadius($r('sys.float.ohos_id_corner_radius_card')) 690 .padding(15) 691 .height(48) 692 } 693 694 @Builder 695 MyMenu() { 696 Column() { 697 SelectionMenu(this.selectionMenuOptions) 698 } 699 .width(256) 700 .backgroundColor(Color.Transparent) 701 } 702 703 @Builder 704 MyMenu2() { 705 Column() { 706 SelectionMenu({ 707 editorMenuOptions: this.editorMenuOptions, 708 expandedMenuOptions: this.expandedMenuOptions1, 709 controller: this.controller, 710 }) 711 } 712 .width(256) 713 .backgroundColor(Color.Transparent) 714 } 715 716 @Builder 717 MyMenu3() { 718 Column() { 719 SelectionMenu({ 720 editorMenuOptions: this.editorMenuOptions1, 721 expandedMenuOptions: this.expandedMenuOptions, 722 controller: this.controller, 723 }) 724 } 725 .width(256) 726 .backgroundColor(Color.Transparent) 727 } 728 729 build() { 730 Column() { 731 Button("SetSelection") 732 .onClick((event: ClickEvent) => { 733 if (this.controller) { 734 this.controller.setSelection(0, 2); 735 } 736 }) 737 738 RichEditor(this.options) 739 .onReady(() => { 740 this.controller.addTextSpan(this.message, { style: { fontColor: Color.Orange, fontSize: 30 } }); 741 this.controller.addTextSpan(this.message, { style: { fontColor: Color.Black, fontSize: 25 } }); 742 }) 743 .onSelect((value: RichEditorSelection) => { 744 if (value.selection[0] == -1 && value.selection[1] == -1) { 745 return; 746 } 747 this.start = value.selection[0]; 748 this.end = value.selection[1]; 749 }) 750 .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu3(), RichEditorResponseType.RIGHT_CLICK) 751 .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu2(), RichEditorResponseType.SELECT) 752 .borderWidth(1) 753 .borderColor(Color.Red) 754 .width(200) 755 .height(200) 756 } 757 } 758} 759``` 760 761 762