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