1# RichEditor 2 3支持图文混排和文本交互式编辑的组件。 4 5> **说明:** 6> 7> 该组件从API Version 10开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。 8> 9> RichEditor[仅支持通过onDragStart事件](ts-universal-events-drag-drop.md)实现浮起等拖拽效果。 10 11 12## 子组件 13 14不包含子组件。 15 16 17## 接口 18 19RichEditor(value: RichEditorOptions) 20 21**参数:** 22 23| 参数名 | 参数类型 | 必填 | 参数描述 | 24| ----- | --------------------------------------- | ---- | ----------- | 25| value | [RichEditorOptions](#richeditoroptions) | 是 | 富文本组件初始化选项。 | 26 27 28## 属性 29 30除支持[通用属性](ts-universal-attributes-size.md)外,还支持以下属性: 31 32> **说明:** 33> 34> 其中clip属性默认值为true。 35> align属性只支持上方丶中间和下方位置的对齐方式。 36 37| 名称 | 参数类型 | 描述 | 38| -------------------------------- | ---------------------------------------- | ---------------------------------------- | 39| customKeyboard | [CustomBuilder](ts-types.md#custombuilder8) | 设置自定义键盘。<br/>**说明:**<br/>当设置自定义键盘时,输入框激活后不会打开系统输入法,而是加载指定的自定义组件。<br/>自定义键盘的高度可以通过自定义组件根节点的height属性设置,宽度不可设置,使用系统默认值。<br/>自定义键盘采用覆盖原始界面的方式呈现,不会对应用原始界面产生压缩或者上提。<br/>自定义键盘无法获取焦点,但是会拦截手势事件。<br/>默认在输入控件失去焦点时,关闭自定义键盘。<br/>如果设备支持拍摄输入,设置自定义键盘后,该输入框会不支持拍摄输入。 | 40| bindSelectionMenu | {<br/>spantype: [RichEditorSpanType](#richeditorspantype),<br/>content: [CustomBuilder](ts-types.md#custombuilder8),<br/>responseType: [ResponseType](ts-appendix-enums.md#responsetype8) \| [RichEditorResponseType<sup>11+</sup>](ts-appendix-enums.md#richeditorresponsetype11),<br/>options?: [SelectionMenuOptions](#selectionmenuoptions11)<br/>} | 设置自定义选择菜单。<br/> 默认值:{<br/> spanType: RichEditorSpanType.TEXT<br/>responseType: ResponseType.LongPress<br/>其他:空<br/>}<br/>自定义菜单超长时,建议内部嵌套[Scroll](./ts-container-scroll.md)组件使用,避免键盘被遮挡。 | 41| copyOptions | [CopyOptions](ts-appendix-enums.md#copyoptions9) | 组件支持设置文本内容是否可复制粘贴。<br />默认值:CopyOptions.LocalDevice <br/>**说明:** <br/>copyOptions不为CopyOptions.None时,长按组件内容,会弹出文本选择弹框。如果通过bindSelectionMenu等方式自定义文本选择菜单,则会弹出自定义的菜单。<br/>设置copyOptions为CopyOptions.None,复制、剪切功能不生效。 | 42| enableDataDetector<sup>11+</sup> | boolean | 使能文本识别。<br/>默认值: false<br/>**说明:**<br/>所识别实体的`fontColor`和`decoration`会被更改为如下样式:<br/>fontColor:Color.Blue<br/>decoration: {<br/>type: TextDecorationType.Underline,<br/>color: Color.Blue<br/>}<br/>该接口依赖设备底层应具有文本识别能力,否则设置不会生效。<br/>当`enableDataDetector`设置为true,同时不设置`dataDetectorConfig`属性时,默认识别所有类型的实体。<br/>当`copyOptions`设置为CopyOptions.None时,该功能不会生效。<br/>对`addBuilderSpan`的节点文本,该功能不会生效。 | 43| dataDetectorConfig<sup>11+</sup> | [TextDataDetectorConfig](#textdatadetectorconfig11) | 文本识别配置。 <br/>默认值:{<br/>types: [ ],<br/>onDetectResultUpdate: null<br/>} <br />**说明:**<br/>需配合`enableDataDetector`一起使用,设置`enableDataDetector`为true时,`dataDetectorConfig`的配置才能生效。<br/> | 44 45## 事件 46 47除支持[通用事件](ts-universal-events-click.md)外,还支持以下事件: 48 49| 名称 | 功能描述 | 50| ---------------------------------------- | ---------------------------------------- | 51| onReady(callback: () => void) | 富文本组件初始化完成后,触发回调。 | 52| onSelect(callback: (value: [RichEditorSelection](#richeditorselection)) => void) | 鼠标左键按下选择,松开左键后触发回调。<br />用手指选择时,松开手指触发回调。 <br />- value:选中的所有span信息。 | 53| aboutToIMEInput(callback: (value: [RichEditorInsertValue](#richeditorinsertvalue)) => boolean) | 输入法输入内容前,触发回调。<br />- value:输入法将要输入内容信息。 | 54| onIMEInputComplete(callback: (value: [RichEditorTextSpanResult](#richeditortextspanresult)) => void) | 输入法完成输入后,触发回调。<br />- value:输入法完成输入后的文本Span信息。 | 55| aboutToDelete(callback: (value: [RichEditorDeleteValue](#richeditordeletevalue)) => boolean) | 输入法删除内容前,触发回调。 <br />- value:准备删除的内容所在的文本Span信息。 | 56| onDeleteComplete(callback: () => void) | 输入法完成删除后,触发回调。 | 57| onPaste<sup>11+</sup>(callback: (event?: [PasteEvent](#pasteevent11)) => void) | 完成粘贴前,触发回调。 <br/>**说明:** <br/>系统的默认粘贴和拖拽行为,只支持纯文本的粘贴。<br/>开发者可以通过该方法,覆盖系统默认行为,实现图文的粘贴。 | 58 59## RichEditorInsertValue 60 61插入文本信息。 62 63| 名称 | 类型 | 必填 | 说明 | 64| ------------ | ------ | ---- | ---------- | 65| insertOffset | number | 是 | 插入的文本偏移位置。 | 66| insertValue | string | 是 | 插入的文本内容。 | 67 68 69## RichEditorDeleteValue 70 71| 名称 | 类型 | 必填 | 说明 | 72| --------------------- | ---------------------------------------- | ---- | ------------------- | 73| offset | number | 是 | 删除内容的偏移位置。 | 74| direction | [RichEditorDeleteDirection](#richeditordeletedirection) | 是 | 删除操作的方向。 | 75| length | number | 是 | 删除内容长度。 | 76| richEditorDeleteSpans | Array<[RichEditorTextSpanResult](#richeditortextspanresult) \| [RichEditorImageSpanResult](#richeditorimagespanresult)> | 是 | 删除的文本或者图片Span的具体信息。 | 77 78 79## RichEditorDeleteDirection 80 81删除操作的方向。 82 83| 名称 | 描述 | 84| -------- | ----- | 85| BACKWARD | 向后删除。 | 86| FORWARD | 向前删除。 | 87 88 89## RichEditorTextSpanResult 90 91文本Span信息。 92 93| 名称 | 类型 | 必填 | 说明 | 94| ----------------------------- | ---------------------------------------- | ---- | ---------------------- | 95| spanPosition | [RichEditorSpanPosition](#richeditorspanposition) | 是 | Span位置。 | 96| value | string | 是 | 文本Span内容。 | 97| textStyle | [RichEditorTextStyleResult](#richeditortextstyleresult) | 是 | 文本Span样式信息。 | 98| offsetInSpan | [number, number] | 是 | 文本Span内容里有效内容的起始和结束位置。 | 99| valueResource<sup>11+</sup> | [Resource](ts-types.md#resource) | 否 | 组件SymbolSpan内容。 | 100| SymbolSpanStyle<sup>11+</sup> | [RichEditorSymbolSpanStyle](#richeditorsymbolspanstyle11) | 否 | 组件SymbolSpan样式信息。 | 101 102 103## RichEditorSpanPosition 104 105Span位置信息。 106 107| 名称 | 类型 | 必填 | 说明 | 108| --------- | ---------------- | ---- | --------------------------- | 109| spanIndex | number | 是 | Span索引值。 | 110| spanRange | [number, number] | 是 | Span内容在RichEditor内的起始和结束位置。 | 111 112## RichEditorSpanType 113 114Span类型信息。 115 116| 名称 | 值 | 描述 | 117| ----- | ---- | ------------ | 118| TEXT | 0 | Span为文字类型。 | 119| IMAGE | 1 | Span为图像类型。 | 120| MIXED | 2 | Span为图文混合类型。 | 121 122## RichEditorTextStyleResult 123 124后端返回的文本样式信息。 125 126| 名称 | 类型 | 必填 | 描述 | 127| ---------- | ---------------------------------------- | ---- | ------------ | 128| fontColor | [ResourceColor](ts-types.md#resourcecolor) | 是 | 文本颜色。 | 129| fontSize | number | 是 | 字体大小。 | 130| fontStyle | [FontStyle](ts-appendix-enums.md#fontstyle) | 是 | 字体样式。 | 131| fontWeight | number | 是 | 字体粗细。 | 132| fontFamily | string | 是 | 字体列表。 | 133| decoration | {<br/>type: [TextDecorationType](ts-appendix-enums.md#textdecorationtype),<br/>color: [ResourceColor](ts-types.md#resourcecolor)<br/>} | 是 | 文本装饰线样式及其颜色。 | 134 135## RichEditorImageSpanResult 136 137后端返回的图片信息。 138 139| 名称 | 类型 | 必填 | 描述 | 140|------------------|-------------------------------------------------------------------|-----|------------------| 141| spanPosition | [RichEditorSpanPosition](#richeditorspanposition) | 是 | Span位置。 | 142| valuePixelMap | [PixelMap](../../apis-image-kit/js-apis-image.md#pixelmap7) | 否 | 图片内容。 | 143| valueResourceStr | [ResourceStr](ts-types.md#resourcestr) | 否 | 图片资源id。 | 144| imageStyle | [RichEditorImageSpanStyleResult](#richeditorimagespanstyleresult) | 是 | 图片样式。 | 145| offsetInSpan | [number, number] | 是 | Span里图片的起始和结束位置。 | 146 147## RichEditorImageSpanStyleResult 148 149后端返回的图片样式信息。 150 151| 名称 | 类型 | 必填 | 描述 | 152| ------------- | ---------------------------------------- | ---- | --------- | 153| size | [number, number] | 是 | 图片的宽度和高度。 | 154| verticalAlign | [ImageSpanAlignment](ts-basic-components-imagespan.md#imagespanalignment) | 是 | 图片垂直对齐方式。 | 155| objectFit | [ImageFit](ts-appendix-enums.md#imagefit) | 是 | 图片缩放类型。 | 156 157## RichEditorOptions 158 159RichEditor初始化参数。 160 161| 名称 | 类型 | 必填 | 说明 | 162| ---------- | ---------------------------------------- | ---- | ------- | 163| controller | [RichEditorController](#richeditorcontroller) | 是 | 富文本控制器。 | 164 165## TextDataDetectorConfig<sup>11+</sup> 166 167文本识别配置参数。 168 169| 名称 | 类型 | 必填 | 说明 | 170| ---------- |-----------------------------------------------------------------------| ---- | ------- | 171| types | [TextDataDetectorType](ts-appendix-enums.md#textdatadetectortype11)[] | 是 | 文本识别的实体类型。设置`types`为`null`或者`[]`时,识别所有类型的实体,否则只识别指定类型的实体。 | 172| onDetectResultUpdate | (result: string) => void | 否 | 文本识别成功后,触发`onDetectResultUpdate`回调。<br/>`result`:文本识别的结果,Json格式。 | 173 174## RichEditorController 175 176RichEditor组件的控制器。 177 178### 导入对象 179 180``` 181controller: RichEditorController = new RichEditorController() 182``` 183 184### getCaretOffset 185 186getCaretOffset(): number 187 188返回当前光标所在位置。 189 190**返回值:** 191 192| 类型 | 说明 | 193| ------ | --------- | 194| number | 当前光标所在位置。 | 195 196### setCaretOffset 197 198setCaretOffset(offset: number): boolean 199 200设置光标位置。 201 202**参数:** 203 204| 参数名 | 参数类型 | 必填 | 参数描述 | 205| ------ | ------ | ---- | -------------------- | 206| offset | number | 是 | 光标偏移位置。超出文本范围时,设置失败。 | 207 208**返回值:** 209 210| 类型 | 说明 | 211| ------- | --------- | 212| boolean | 光标是否设置成功。 | 213 214### addTextSpan 215 216addTextSpan(value: string, options?: RichEditorTextSpanOptions): number 217 218添加文本内容。 219 220**参数:** 221 222| 参数名 | 参数类型 | 必填 | 参数描述 | 223| ------- | ---------------------------------------- | ---- | ----- | 224| value | string | 是 | 文本内容。 | 225| options | [RichEditorTextSpanOptions](#richeditortextspanoptions) | 否 | 文本选项。 | 226 227**返回值:** 228 229| 类型 | 说明 | 230| ------ | -------------------- | 231| number | 添加完成的Text Span所在的位置。 | 232 233### addImageSpan 234 235addImageSpan(value: PixelMap | ResourceStr, options?: RichEditorImageSpanOptions): number 236 237添加图片内容。 238 239**参数:** 240 241| 参数名 | 参数类型 | 必填 | 参数描述 | 242| ------- | ---------------------------------------- | ---- | ----- | 243| value | [PixelMap](../../apis-image-kit/js-apis-image.md#pixelmap7)\|[ResourceStr](ts-types.md#resourcestr) | 是 | 图片内容。 | 244| options | [RichEditorImageSpanOptions](#richeditorimagespanoptions) | 否 | 图片选项。 | 245 246**返回值:** 247 248| 类型 | 说明 | 249| ------ | -------------------- | 250| number | 添加完成的imageSpan所在的位置。 | 251 252### addBuilderSpan<sup>11+</sup> 253 254addBuilderSpan(value: CustomBuilder, options?: RichEditorBuilderSpanOptions): number 255 256> **说明:** 257> 258> - RichEditor组件添加占位Span,占位Span调用系统的measure方法计算真实的长宽和位置。 259> - 可通过[RichEditorBuilderSpanOptions](#richeditorbuilderspanoptions11)设置此builder在RichEditor中的index(一个文字为一个单位)。 260> - 此占位Span不可获焦,不支持拖拽(与可拖拽Span如文本或图片Span一起拖拽时,此Span对应的位置内容不会显示但会占相同大小的区域),支持部分通用属性,占位、删除等能力等同于ImageSpan,长度视为一个文字。 261> - 不支持通过[bindSelectionMenu](#属性)设置自定义菜单。 262> - 不支持通过[getSpans](#getspans),[getSelection](#getselection11),[onSelect](#事件),[aboutToDelete](#事件)获取builderSpan信息。 263> - 不支持通过[updateSpanStyle](#updatespanstyle),[updateParagraphStyle](#updateparagraphstyle11)等方式更新builder。 264> - 对此builder节点进行复制或粘贴不生效。 265> - builder的布局约束由RichEditor传入,如果builder里最外层组件不设置大小,则会用RichEditor的大小作为maxSize。 266> - builder的手势相关事件机制与通用手势事件相同,如果builder中未设置透传,则仅有builder中的子组件响应。 267 268通用属性仅支持[size](ts-universal-attributes-size.md#size)、[padding](ts-universal-attributes-size.md#padding)、[margin](ts-universal-attributes-size.md#margin)、[aspectRatio](ts-universal-attributes-layout-constraints.md#aspectratio)、[borderStyle](ts-universal-attributes-border.md#borderstyle)、[borderWidth](ts-universal-attributes-border.md#borderwidth)、[borderColor](ts-universal-attributes-border.md#bordercolor)、[borderRadius](ts-universal-attributes-border.md#borderradius)、[backgroundColor](ts-universal-attributes-background.md#backgroundcolor)、[backgroundBlurStyle](ts-universal-attributes-background.md#backgroundblurstyle9)、[opacity](ts-universal-attributes-opacity.md)、[blur](ts-universal-attributes-image-effect.md#blur)、[backdropBlur](ts-universal-attributes-image-effect.md#backdropblur)、[shadow](ts-universal-attributes-image-effect.md#shadow)、[grayscale](ts-universal-attributes-image-effect.md#grayscale)、[brightness](ts-universal-attributes-image-effect.md#brightness)、[saturate](ts-universal-attributes-image-effect.md#saturate)、 269[contrast](ts-universal-attributes-image-effect.md#contrast)、[invert](ts-universal-attributes-image-effect.md#invert)、[sepia](ts-universal-attributes-image-effect.md#sepia)、[hueRotate](ts-universal-attributes-image-effect.md#huerotate)、[colorBlend](ts-universal-attributes-image-effect.md#colorblend7)、[linearGradientBlur](ts-universal-attributes-image-effect.md#lineargradientblur10)、[clip](ts-universal-attributes-sharp-clipping.md#clip)、[mask](ts-universal-attributes-sharp-clipping.md#mask)、[foregroundBlurStyle](ts-universal-attributes-foreground-blur-style.md#foregroundblurstyle)、[accessibilityGroup](ts-universal-attributes-accessibility.md#accessibilitygroup)、[accessibilityText](ts-universal-attributes-accessibility.md#accessibilitytext)、[accessibilityDescription](ts-universal-attributes-accessibility.md#accessibilitydescription)、[accessibilityLevel](ts-universal-attributes-accessibility.md#accessibilitylevel)、[sphericalEffect](ts-universal-attributes-image-effect-sys.md#sphericaleffect10)、[lightUpEffect](ts-universal-attributes-image-effect-sys.md#lightupeffect10)、[pixelStretchEffect](ts-universal-attributes-image-effect-sys.md#pixelstretcheffect10)。 270 271**参数:** 272 273| 参数名 | 参数类型 | 必填 | 参数描述 | 274| ------- | ---------------------------------------- | ---- | ---------- | 275| value | [CustomBuilder](ts-types.md#custombuilder8) | 是 | 自定义组件。 | 276| options | [RichEditorBuilderSpanOptions](#richeditorbuilderspanoptions11) | 否 | builder选项。 | 277 278**返回值:** 279 280| 类型 | 说明 | 281| ------ | ---------------------- | 282| number | 添加完成的builderSpan所在的位置。 | 283 284### addSymbolSpan<sup>11+</sup> 285 286addSymbolSpan(value: Resource, options?: RichEditorSymbolSpanOptions ): number 287 288在Richeditor中添加SymbolSpan。 289 290暂不支持手势处理。 291 292**参数:** 293 294| 参数名 | 参数类型 | 必填 | 参数描述 | 295| ------- | ---------------------------------------- | ---- | ----- | 296| value | [Resource](ts-types.md#resource) | 是 | 组件内容。 | 297| options | [RichEditorSymbolSpanOptions](#richeditorsymbolspanoptions11) | 否 | 组件选项。 | 298 299**返回值:** 300 301| 类型 | 说明 | 302| ------ | --------------------- | 303| number | 添加完成的SymbolSpan所在的位置。 | 304 305### getTypingStyle<sup>11+</sup> 306 307getTypingStyle(): RichEditorTextStyle 308 309获得用户预设的样式。 310 311**返回值:** 312 313| 类型 | 说明 | 314| ---------------------------------------- | ------- | 315| [RichEditorTextStyle](#richeditortextstyle) | 用户预设样式。 | 316 317### setTypingStyle<sup>11+</sup> 318 319setTypingStyle(value: RichEditorTextStyle): void 320 321设置用户预设的样式。 322 323**参数:** 324 325| 参数名 | 参数类型 | 必填 | 参数描述 | 326| ----- | ---------------------------------------- | ---- | ----- | 327| value | [RichEditorTextStyle](#richeditortextstyle) | 是 | 预设样式。 | 328 329### updateSpanStyle 330 331updateSpanStyle(value: RichEditorUpdateTextSpanStyleOptions | RichEditorUpdateImageSpanStyleOptions | RichEditorUpdateSymbolSpanStyleOptions): void 332 333更新文本或者图片样式。<br/>若只更新了一个Span的部分内容,则会根据更新部分、未更新部分将该Span拆分为多个Span。 334 335使用该接口更新文本或图片样式时默认不会关闭自定义文本选择菜单。 336 337**参数:** 338 339| 名称 | 类型 | 必填 | 描述 | 340| ------ | -------- | ---- | -------------------------------------- | 341| value | [RichEditorUpdateTextSpanStyleOptions](#richeditorupdatetextspanstyleoptions) \| [RichEditorUpdateImageSpanStyleOptions](#richeditorupdateimagespanstyleoptions) \| [RichEditorUpdateSymbolSpanStyleOptions](#richeditorupdatesymbolspanstyleoptions11)<sup>11+</sup> | 是 | 文本或者图片的样式选项信息。 | 342 343### updateParagraphStyle<sup>11+</sup> 344 345updateParagraphStyle(value: RichEditorParagraphStyleOptions): void 346 347更新段落的样式。 348 349**参数:** 350 351| 名称 | 类型 | 必填 | 描述 | 352| ----- | ---------------------------------------- | ---- | ---------- | 353| value | [RichEditorParagraphStyleOptions](#richeditorparagraphstyleoptions11) | 是 | 段落的样式选项信息。 | 354 355### getSpans 356 357getSpans(value?: RichEditorRange): Array<RichEditorTextSpanResult| RichEditorImageSpanResult> 358 359获取span信息。 360 361**参数:** 362 363| 参数名 | 参数类型 | 必填 | 参数描述 | 364| ----- | ----------------------------------- | ---- | ----------- | 365| value | [RichEditorRange](#richeditorrange) | 否 | 需要获取span范围。 | 366 367**返回值:** 368 369| 类型 | 说明 | 370| ---------------------------------------- | ------------ | 371| Array<[RichEditorTextSpanResult](#richeditortextspanresult) \| [RichEditorImageSpanResult](#richeditorimagespanresult)> | 文本和图片Span信息。 | 372 373### deleteSpans 374 375deleteSpans(value?: RichEditorRange): void 376 377删除指定范围内的文本和图片。 378 379**参数:** 380 381| 参数名 | 参数类型 | 必填 | 参数描述 | 382| ----- | ----------------------------------- | ---- | ------------------- | 383| value | [RichEditorRange](#richeditorrange) | 否 | 删除范围。省略时,删除所有文本和图片。 | 384 385### getParagraphs<sup>11+</sup> 386 387getParagraphs(value?: RichEditorRange): Array\<RichEditorParagraphResult> 388 389获得指定返回的段落。 390 391**参数:** 392 393| 参数名 | 参数类型 | 必填 | 参数描述 | 394| ----- | ----------------------------------- | ---- | ---------- | 395| value | [RichEditorRange](#richeditorrange) | 否 | 需要获取段落的范围。 | 396 397**返回值:** 398 399| 类型 | 说明 | 400| ---------------------------------------- | -------- | 401| Array\<[RichEditorParagraphResult](#richeditorparagraphresult11)> | 选中段落的信息。 | 402 403### closeSelectionMenu 404 405closeSelectionMenu(): void 406 407关闭自定义选择菜单或系统默认选择菜单。 408 409### setSelection<sup>11+</sup> 410 411setSelection(selectionStart: number, selectionEnd: number) 412 413支持设置文本选中,选中部分背板高亮。 414 415selectionStart和selectionEnd均为-1时表示全选。 416 417接口调用前有带手柄菜单弹出时则调用后不主动关闭菜单,且调整菜单位置。 418 419接口调用前有不带手柄菜单弹出时则调用后不主动关闭菜单,且保持菜单原来位置。 420 421接口调用前无菜单弹出,则调用后也无菜单弹出。 422 423未获焦时调用该接口不产生选中效果。 424 425使用[示例](ts-composite-components-selectionmenu.md#示例)。 426 427**参数:** 428 429| 参数名 | 参数类型 | 必填 | 参数描述 | 430| -------------- | ------ | ---- | ------- | 431| selectionStart | number | 是 | 选中开始位置。 | 432| selectionEnd | number | 是 | 选中结束位置。 | 433 434### getSelection<sup>11+</sup> 435 436getSelection(): RichEditorSelection 437 438获取选中文本内容。如果未选中内容,返回光标所在span信息。 439 440使用[示例](ts-composite-components-selectionmenu.md#示例)。 441 442**返回值:** 443 444| 类型 | 说明 | 445| ---------------------------------------- | ------- | 446| [RichEditorSelection](#richeditorselection) | 选中内容信息。 | 447 448## RichEditorSelection 449 450选中内容信息。 451 452| 名称 | 类型 | 必填 | 说明 | 453| --------- | ---------------------------------------- | ---- | ------- | 454| selection | [number, number] | 是 | 选中范围。 | 455| spans | Array<[RichEditorTextSpanResult](#richeditortextspanresult)\| [RichEditorImageSpanResult](#richeditorimagespanresult)> | 是 | span信息。 | 456 457 458## RichEditorUpdateTextSpanStyleOptions 459 460文本样式选项。 461 462| 名称 | 类型 | 必填 | 描述 | 463| --------- | ---------------------------------------- | ---- | ------------------------------- | 464| start | number | 否 | 需要更新样式的文本起始位置,省略或者设置负值时表示从0开始。 | 465| end | number | 否 | 需要更新样式的文本结束位置,省略或者超出文本范围时表示无穷大。 | 466| textStyle | [RichEditorTextStyle](#richeditortextstyle) | 是 | 文本样式。 | 467 468> **说明:** 469> 470> 当start大于end时为异常情况,此时start为0,end为无穷大。 471 472## RichEditorUpdateImageSpanStyleOptions 473 474图片样式选项。 475 476| 名称 | 类型 | 必填 | 描述 | 477| ---------- | ---------------------------------------- | ---- | ------------------------------- | 478| start | number | 否 | 需要更新样式的图片起始位置,省略或者设置负值时表示从0开始。 | 479| end | number | 否 | 需要更新样式的图片结束位置,省略或者超出文本范围时表示无穷大。 | 480| imageStyle | [RichEditorImageSpanStyle](#richeditorimagespanstyle) | 是 | 图片样式。 | 481 482> **说明:** 483> 484> 当start大于end时为异常情况,此时start为0,end为无穷大。 485 486## RichEditorUpdateSymbolSpanStyleOptions<sup>11+</sup> 487 488SymbolSpan样式选项。 489 490| 名称 | 类型 | 必填 | 描述 | 491| ----------- | ---------------------------------------- | ---- | ------------------------------- | 492| start | number | 否 | 需要更新样式的文本起始位置,省略或者设置负值时表示从0开始。 | 493| end | number | 否 | 需要更新样式的文本结束位置,省略或者超出文本范围时表示无穷大。 | 494| symbolStyle | [RichEditorSymbolSpanStyle](#richeditorsymbolspanstyle11) | 是 | 组件样式。 | 495 496> **说明:** 497> 498> 当start大于end时为异常情况,此时start为0,end为无穷大。 499 500## RichEditorParagraphStyleOptions<sup>11+</sup> 501 502段落样式选项 503 504| 名称 | 类型 | 必填 | 描述 | 505| ----- | ---------------------------------------- | ---- | ---------------------------------- | 506| start | number | 否 | 需要更新样式的段落起始位置,省略或者设置负值时表示从0开始。 | 507| end | number | 否 | 需要更新样式的段落结束位置,省略、负数或者超出文本范围时表示无穷大。 | 508| style | [RichEditorParagraphStyle](#richeditorparagraphstyle11) | 是 | 段落样式。 | 509 510> **说明:** 511> 512> 当start大于end时为异常情况,此时start为0,end为无穷大。 513 514## RichEditorParagraphStyle<sup>11+</sup> 515 516段落样式。 517 518| 名称 | 类型 | 必填 | 描述 | 519| ------------- | ---------------------------------------- | ---- | ------------------ | 520| textAlign | [TextAlign](ts-appendix-enums.md#textalign) | 否 | 设置文本段落在水平方向的对齐方式。 | 521| leadingMargin | [Dimension](ts-types.md#dimension10) \| [LeadingMarginPlaceholder](#leadingmarginplaceholder11) | 否 | 设置文本段落缩进,不支持设置百分比,图片放在段首时不支持。 | 522 523## LeadingMarginPlaceholder<sup>11+</sup> 524 525前导边距占位符,用于表示文本段落左侧与组件边缘之间的距离。 526 527| 名称 | 类型 | 必填 | 描述 | 528| -------- | ---------------------------------------- | ---- | -------------- | 529| pixelMap | [PixelMap](../../apis-image-kit/js-apis-image.md#pixelmap7) | 是 | 图片内容。 | 530| size | \[[Dimension](ts-types.md#dimension10), [Dimension](ts-types.md#dimension10)\] | 是 | 图片大小,不支持设置百分比。 | 531 532## RichEditorParagraphResult<sup>11+</sup> 533 534后端返回的段落信息。 535 536| 名称 | 类型 | 必填 | 描述 | 537| ----- | ---------------------------------------- | ---- | ------- | 538| style | [RichEditorParagraphStyle](#richeditorparagraphstyle11) | 是 | 段落样式。 | 539| range | \[number, number\] | 是 | 段落起始和结束位置。 | 540 541## RichEditorTextSpanOptions 542 543添加文本的偏移位置和文本样式信息。 544 545| 名称 | 类型 | 必填 | 描述 | 546| ---------------------------- | ---------------------------------------- | ---- | -------------------------- | 547| offset | number | 否 | 添加文本的位置。省略时,添加到所有文本字符串的最后。<br/>当值小于0时,放在字符串最前面;当值大于字符串长度时,放在字符串最后面。 | 548| style | [RichEditorTextStyle](#richeditortextstyle) | 否 | 文本样式信息。省略时,使用系统默认文本信息。 | 549| paragraphStyle<sup>11+</sup> | [RichEditorParagraphStyle](#richeditorparagraphstyle11) | 否 | 段落样式。 | 550| gesture<sup>11+</sup> | [RichEditorGesture](#richeditorgesture11) | 否 | 行为触发回调。省略时,仅使用系统默认行为。 | 551 552## RichEditorTextStyle 553 554文本样式信息。 555 556| 名称 | 类型 | 必填 | 描述 | 557| ------------------------ | ---------------------------------------- | ---- | ---------------------------------------- | 558| fontColor | [ResourceColor](ts-types.md#resourcecolor) | 否 | 文本颜色。<br/> 默认值:Color.Black。 | 559| fontSize | [Length](ts-types.md#length) \| number | 否 | 设置字体大小,Length为number类型时,使用fp单位。字体默认大小16。不支持设置百分比字符串。<br/>从API version 9开始,该接口支持在ArkTS卡片中使用。 | 560| fontStyle | [FontStyle](ts-appendix-enums.md#fontstyle) | 否 | 字体样式。<br/>默认值:FontStyle.Normal。 | 561| fontWeight | [FontWeight](ts-appendix-enums.md#fontweight) \| number \| string | 否 | 字体粗细。<br/>number类型取值[100,900],取值间隔为100,默认为400,取值越大,字体越粗。<br/>string类型仅支持number类型取值的字符串形式,例如“400”,以及“bold”、“bolder”、“lighter”、“regular” 、“medium”分别对应FontWeight中相应的枚举值。<br/>默认值:FontWeight.Normal。 | 562| fontFamily | [ResourceStr](ts-types.md#resourcestr) | 否 | 设置字体列表。默认字体'HarmonyOS Sans',当前支持'HarmonyOS Sans'字体和[注册自定义字体](../js-apis-font.md)。 <br/>默认字体:'HarmonyOS Sans'。 | 563| decoration | {<br/>type: [TextDecorationType](ts-appendix-enums.md#textdecorationtype),<br/>color?: [ResourceColor](ts-types.md#resourcecolor)<br/>} | 否 | 设置文本装饰线样式及其颜色。<br />默认值:{<br/>type: TextDecorationType.None,<br/>color:Color.Black<br/>}。 | 564| textShadow<sup>11+</sup> | [ShadowOptions](ts-universal-attributes-image-effect.md#shadowoptions对象说明) \| Array<[ShadowOptions](ts-universal-attributes-image-effect.md#shadowoptions对象说明)> | 否 | 设置文字阴影效果。该接口支持以数组形式入参,实现多重文字阴影。<br/>**说明:**<br/>不支持fill字段, 不支持智能取色模式。 | 565 566 567## RichEditorImageSpanOptions 568 569添加图片的偏移位置和图片样式信息。 570 571| 名称 | 类型 | 必填 | 描述 | 572| --------------------- | ---------------------------------------- | ---- | -------------------------- | 573| offset | number | 否 | 添加图片的位置。省略时,添加到所有文本字符串的最后。<br/>当值小于0时,放在字符串最前面;当值大于字符串长度时,放在字符串最后面。 | 574| imageStyle | [RichEditorImageSpanStyle](#richeditorimagespanstyle) | 否 | 图片样式信息。省略时,使用系统默认图片信息。 | 575| gesture<sup>11+</sup> | [RichEditorGesture](#richeditorgesture11) | 否 | 行为触发回调。省略时,仅使用系统默认行为。 | 576 577## RichEditorImageSpanStyle 578 579图片样式。 580 581| 名称 | 类型 | 必填 | 描述 | 582| ------------------------- | ---------------------------------------- | ---- | ---------------------------------------- | 583| size | [[Dimension](ts-types.md#dimension10), [Dimension](ts-types.md#dimension10)] | 否 | 图片宽度和高度。 | 584| verticalAlign | [ImageSpanAlignment](ts-basic-components-imagespan.md#imagespanalignment) | 否 | 图片垂直对齐方式。<br/>默认值:ImageSpanAlignment.BASELINE | 585| objectFit | [ImageFit](ts-appendix-enums.md#imagefit) | 否 | 图片缩放类型。<br/> 默认值:ImageFit.Cover。 | 586| layoutStyle<sup>11+</sup> | [RichEditorLayoutStyle](#richeditorlayoutstyle11) | 否 | 图片布局风格。<br/> | 587 588## RichEditorLayoutStyle<sup>11+</sup> 589|名称 |类型 |必填| 描述| 590| ------------- | ----------------------- | ---- | ------------------------------------------------------------ | 591|margin | [Dimension](ts-types.md#dimension10) \| [Margin](ts-types.md#margin) | 否 | 外边距类型,用于描述组件不同方向的外边距。<br/>参数为Dimension类型时,四个方向外边距同时生效。| 592|borderRadius | [Dimension](ts-types.md#dimension10) \| [BorderRadiuses](ts-types.md#borderradiuses9) | 否 | 圆角类型,用于描述组件边框圆角半径。| 593 594## RichEditorSymbolSpanOptions<sup>11+</sup> 595 596添加文本的偏移位置和文本样式信息。 597 598| 名称 | 类型 | 必填 | 描述 | 599| ------ | ---------------------------------------- | ---- | -------------------------- | 600| offset | number | 否 | 添加组件的位置。省略时,添加到所有文本字符串的最后。<br/>当值小于0时,放在字符串最前面;当值大于字符串长度时,放在字符串最后面。 | 601| style | [RichEditorSymbolSpanStyle](#richeditorsymbolspanstyle11) | 否 | 组件样式信息。省略时,使用系统默认文本信息。 | 602 603## RichEditorSymbolSpanStyle<sup>11+</sup> 604 605组件SymbolSpan样式信息。 606 607| 名称 | 类型 | 必填 | 描述 | 608| ------ | -------- | ---- | -------------------------------------- | 609| fontColor | Array\<[ResourceColor](ts-types.md#resourcecolor)\> | 否 | 设置SymbolGlyph组件颜色。<br/> 默认值:不同渲染策略下默认值不同。 | 610| fontSize | number \| string \| [Resource](ts-types.md#resource) | 否 | 设置SymbolGlyph组件大小。<br/>默认值:系统默认值。 | 611| fontWeight | [FontWeight](ts-appendix-enums.md#fontweight) \| number \| string | 否 | 设置SymbolGlyph组件粗细。<br/>number类型取值[100,900],取值间隔为100,默认为400,取值越大,字体越粗。<br/>string类型仅支持number类型取值的字符串形式,例如“400”,以及“bold”、“bolder”、“lighter”、“regular” 、“medium”分别对应FontWeight中相应的枚举值。<br/>默认值:FontWeight.Normal。 | 612| renderingStrategy | [SymbolRenderingStrategy](ts-appendix-enums.md#symbolrenderingstrategy11) | 否 | 设置SymbolGlyph组件渲染策略。<br/>默认值:SymbolRenderingStrategy.SINGLE。<br/>**说明:**<br/>$r('sys.symbol.ohos_*')中引用的资源仅ohos_trash_circle、ohos_folder_badge_plus、ohos_lungs支持分层与多色模式。 | 613| effectStrategy | [SymbolEffectStrategy](ts-appendix-enums.md#symboleffectstrategy11) | 否 | 设置SymbolGlyph组件动效策略。<br/>默认值:SymbolEffectStrategy.NONE。<br/>**说明:**<br/>$r('sys.symbol.ohos_*')中引用的资源仅ohos_wifi支持层级动效模式。 | 614 615## RichEditorBuilderSpanOptions<sup>11+</sup> 616 617添加图片的偏移位置和图片样式信息。 618 619| 名称 | 类型 | 必填 | 描述 | 620| ------ | ------ | ---- | ------------------------------------- | 621| offset | number | 否 | 添加builder的位置。省略或者为异常值时,添加到所有文本字符串的最后。 | 622 623## RichEditorRange 624 625范围信息。 626 627| 名称 | 类型 | 必填 | 描述 | 628| ----- | ------ | ---- | ---------------------- | 629| start | number | 否 | 起始位置,省略或者设置负值时表示从0开始。 | 630| end | number | 否 | 结束位置,省略或者超出文本范围时表示无穷大。 | 631 632## SelectionMenuOptions<sup>11+</sup> 633 634范围信息。 635 636| 名称 | 类型 | 必填 | 描述 | 637| ----------- | ---------- | ---- | ------------- | 638| onAppear | () => void | 否 | 自定义选择菜单弹出时回调。 | 639| onDisappear | () => void | 否 | 自定义选择菜单关闭时回调。 | 640 641## PasteEvent<sup>11+</sup> 642 643定义用户粘贴事件。 644 645| 名称 | 类型 | 必填 | 描述 | 646| -------------- | ----------- | ---- | ----------------------------- | 647| preventDefault | () => void | 否 | 阻止系统默认粘贴事件。 | 648 649## RichEditorGesture<sup>11+</sup> 650 651用户行为回调。 652 653### onClick<sup>11+</sup> 654 655onClick(callback: (event?: ClickEvent) => void) 656 657点击完成时回调事件。<br/> 658双击时,第一次点击触发回调事件。 659 660**参数:** 661 662| 参数名 | 参数类型 | 必填 | 描述 | 663| ----- | ---------------------------------------- | ---- | ------- | 664| event | [ClickEvent](ts-universal-events-click.md#clickevent对象说明) | 否 | 用户点击事件。 | 665 666### onLongPress<sup>11+</sup> 667 668onLongPress(callback: (event?: GestureEvent) => void ) 669 670长按完成时回调事件。 671 672**参数:** 673 674| 参数名 | 参数类型 | 必填 | 描述 | 675| ----- | ---------------------------------------- | ---- | ------- | 676| event | [GestureEvent](ts-gesture-settings.md#gestureevent对象说明) | 否 | 用户长按事件。 | 677 678## 示例 679 680### 示例1 681 682```ts 683// xxx.ets 684@Entry 685@Component 686struct Index { 687 controller: RichEditorController = new RichEditorController(); 688 options: RichEditorOptions = { controller: this.controller }; 689 private start: number = -1; 690 private end: number = -1; 691 @State message: string = "[-1, -1]" 692 @State content: string = "" 693 694 build() { 695 Column() { 696 Column() { 697 Text("selection range:").width("100%") 698 Text() { 699 Span(this.message) 700 }.width("100%") 701 Text("selection content:").width("100%") 702 Text() { 703 Span(this.content) 704 }.width("100%") 705 } 706 .borderWidth(1) 707 .borderColor(Color.Red) 708 .width("100%") 709 .height("20%") 710 711 Row() { 712 Button("更新样式:加粗").onClick(() => { 713 this.controller.updateSpanStyle({ 714 start: this.start, 715 end: this.end, 716 textStyle: 717 { 718 fontWeight: FontWeight.Bolder 719 } 720 }) 721 }) 722 Button("获取选择内容").onClick(() => { 723 this.content = ""; 724 this.controller.getSpans({ 725 start: this.start, 726 end: this.end 727 }).forEach(item => { 728 if(typeof(item as RichEditorImageSpanResult)['imageStyle'] != 'undefined'){ 729 this.content += (item as RichEditorImageSpanResult).valueResourceStr; 730 this.content += "\n" 731 } else { 732 if(typeof(item as RichEditorTextSpanResult)['symbolSpanStyle'] != 'undefined') { 733 this.content += (item as RichEditorTextSpanResult).symbolSpanStyle?.fontSize; 734 this.content += "\n" 735 }else { 736 this.content += (item as RichEditorTextSpanResult).value; 737 this.content += "\n" 738 } 739 } 740 }) 741 }) 742 Button("删除选择内容").onClick(() => { 743 this.controller.deleteSpans({ 744 start: this.start, 745 end: this.end 746 }) 747 this.start = -1; 748 this.end = -1; 749 this.message = "[" + this.start + ", " + this.end + "]" 750 }) 751 } 752 .borderWidth(1) 753 .borderColor(Color.Red) 754 .width("100%") 755 .height("10%") 756 757 Column() { 758 RichEditor(this.options) 759 .onReady(() => { 760 this.controller.addTextSpan("012345", 761 { 762 style: 763 { 764 fontColor: Color.Orange, 765 fontSize: 30 766 } 767 }) 768 this.controller.addSymbolSpan($r("sys.symbol.ohos_trash"), 769 { 770 style: 771 { 772 fontSize: 30 773 } 774 }) 775 this.controller.addImageSpan($r("app.media.icon"), 776 { 777 imageStyle: 778 { 779 size: ["57px", "57px"] 780 } 781 }) 782 this.controller.addTextSpan("56789", 783 { 784 style: 785 { 786 fontColor: Color.Black, 787 fontSize: 30 788 } 789 }) 790 }) 791 .onSelect((value: RichEditorSelection) => { 792 this.start = value.selection[0]; 793 this.end = value.selection[1]; 794 this.message = "[" + this.start + ", " + this.end + "]" 795 }) 796 .aboutToIMEInput((value: RichEditorInsertValue) => { 797 console.log("---------------------- aboutToIMEInput ----------------------") 798 console.log("insertOffset:" + value.insertOffset) 799 console.log("insertValue:" + value.insertValue) 800 return true; 801 }) 802 .onIMEInputComplete((value: RichEditorTextSpanResult) => { 803 console.log("---------------------- onIMEInputComplete ---------------------") 804 console.log("spanIndex:" + value.spanPosition.spanIndex) 805 console.log("spanRange:[" + value.spanPosition.spanRange[0] + "," + value.spanPosition.spanRange[1] + "]") 806 console.log("offsetInSpan:[" + value.offsetInSpan[0] + "," + value.offsetInSpan[1] + "]") 807 console.log("value:" + value.value) 808 }) 809 .aboutToDelete((value: RichEditorDeleteValue) => { 810 console.log("---------------------- aboutToDelete --------------------------") 811 console.log("offset:" + value.offset) 812 console.log("direction:" + value.direction) 813 console.log("length:" + value.length) 814 value.richEditorDeleteSpans.forEach(item => { 815 console.log("---------------------- item --------------------------") 816 console.log("spanIndex:" + item.spanPosition.spanIndex) 817 console.log("spanRange:[" + item.spanPosition.spanRange[0] + "," + item.spanPosition.spanRange[1] + "]") 818 console.log("offsetInSpan:[" + item.offsetInSpan[0] + "," + item.offsetInSpan[1] + "]") 819 if (typeof(item as RichEditorImageSpanResult)['imageStyle'] != 'undefined') { 820 console.log("image:" + (item as RichEditorImageSpanResult).valueResourceStr) 821 } else { 822 console.log("text:" + (item as RichEditorTextSpanResult).value) 823 } 824 }) 825 return true; 826 }) 827 .onDeleteComplete(() => { 828 console.log("---------------------- onDeleteComplete ------------------------") 829 }) 830 .placeholder("input...", { 831 fontColor: Color.Gray, 832 font: { 833 size: 16, 834 weight: FontWeight.Normal, 835 family: "HarmonyOS Sans", 836 style: FontStyle.Normal 837 } 838 }) 839 .borderWidth(1) 840 .borderColor(Color.Green) 841 .width("100%") 842 .height("30%") 843 } 844 .borderWidth(1) 845 .borderColor(Color.Red) 846 .width("100%") 847 .height("70%") 848 } 849 } 850} 851``` 852 853 854### 示例2 855 856```ts 857// xxx.ets 858@Entry 859@Component 860struct RichEditorExample { 861 controller: RichEditorController = new RichEditorController() 862 863 // 自定义键盘组件 864 @Builder CustomKeyboardBuilder() { 865 Column() { 866 Grid() { 867 ForEach([1, 2, 3, 4, 5, 6, 7, 8, 9, '*', 0, '#'], (item: number | string) => { 868 GridItem() { 869 Button(item + "") 870 .width(110).onClick(() => { 871 this.controller.addTextSpan(item + '', { 872 offset: this.controller.getCaretOffset(), 873 style: 874 { 875 fontColor: Color.Orange, 876 fontSize: 30 877 } 878 }) 879 this.controller.setCaretOffset(this.controller.getCaretOffset() + item.toString().length) 880 }) 881 } 882 }) 883 }.maxCount(3).columnsGap(10).rowsGap(10).padding(5) 884 }.backgroundColor(Color.Gray) 885 } 886 887 build() { 888 Column() { 889 RichEditor({ controller: this.controller }) 890 // 绑定自定义键盘 891 .customKeyboard(this.CustomKeyboardBuilder()).margin(10).border({ width: 1 }) 892 .height(200) 893 .borderWidth(1) 894 .borderColor(Color.Red) 895 .width("100%") 896 } 897 } 898} 899``` 900 901 902 903### 示例3 904 905```ts 906// xxx.ets 907import pasteboard from '@ohos.pasteboard' 908import { BusinessError } from '@ohos.base'; 909 910export interface SelectionMenuTheme { 911 imageSize: number; 912 buttonSize: number; 913 menuSpacing: number; 914 editorOptionMargin: number; 915 expandedOptionPadding: number; 916 defaultMenuWidth: number; 917 imageFillColor: Resource; 918 backGroundColor: Resource; 919 iconBorderRadius: Resource; 920 containerBorderRadius: Resource; 921 cutIcon: Resource; 922 copyIcon: Resource; 923 pasteIcon: Resource; 924 selectAllIcon: Resource; 925 shareIcon: Resource; 926 translateIcon: Resource; 927 searchIcon: Resource; 928 arrowDownIcon: Resource; 929 iconPanelShadowStyle: ShadowStyle; 930 iconFocusBorderColor: Resource; 931} 932 933export const defaultTheme: SelectionMenuTheme = { 934 imageSize: 24, 935 buttonSize: 48, 936 menuSpacing: 8, 937 editorOptionMargin: 1, 938 expandedOptionPadding: 3, 939 defaultMenuWidth: 256, 940 imageFillColor: $r('sys.color.ohos_id_color_primary'), 941 backGroundColor: $r('sys.color.ohos_id_color_dialog_bg'), 942 iconBorderRadius: $r('sys.float.ohos_id_corner_radius_default_m'), 943 containerBorderRadius: $r('sys.float.ohos_id_corner_radius_card'), 944 cutIcon: $r("sys.media.ohos_ic_public_cut"), 945 copyIcon: $r("sys.media.ohos_ic_public_copy"), 946 pasteIcon: $r("sys.media.ohos_ic_public_paste"), 947 selectAllIcon: $r("sys.media.ohos_ic_public_select_all"), 948 shareIcon: $r("sys.media.ohos_ic_public_share"), 949 translateIcon: $r("sys.media.ohos_ic_public_translate_c2e"), 950 searchIcon: $r("sys.media.ohos_ic_public_search_filled"), 951 arrowDownIcon: $r("sys.media.ohos_ic_public_arrow_down"), 952 iconPanelShadowStyle: ShadowStyle.OUTER_DEFAULT_MD, 953 iconFocusBorderColor: $r('sys.color.ohos_id_color_focused_outline'), 954} 955 956@Entry 957@Component 958struct SelectionMenu { 959 @State message: string = 'Hello World' 960 @State textSize: number = 40 961 @State sliderShow: boolean = false 962 @State start: number = -1 963 @State end: number = -1 964 @State colorTransparent: Color = Color.Transparent 965 controller: RichEditorController = new RichEditorController(); 966 options: RichEditorOptions = { controller: this.controller } 967 private iconArr: Array<Resource> = 968 [$r('app.media.icon'), $r("app.media.icon"), $r('app.media.icon'), 969 $r("app.media.icon"), $r('app.media.icon')] 970 @State iconBgColor: ResourceColor[] = new Array(this.iconArr.length).fill(this.colorTransparent) 971 @State pasteEnable: boolean = false 972 @State visibilityValue: Visibility = Visibility.Visible 973 @State textStyle: RichEditorTextStyle = {} 974 private fontWeightTable: string[] = ["100", "200", "300", "400", "500", "600", "700", "800", "900", "bold", "normal", "bolder", "lighter", "medium", "regular"] 975 private theme: SelectionMenuTheme = defaultTheme; 976 977 aboutToAppear() { 978 if (this.controller) { 979 let richEditorSelection = this.controller.getSelection() 980 if (richEditorSelection) { 981 let start = richEditorSelection.selection[0] 982 let end = richEditorSelection.selection[1] 983 if (start === 0 && this.controller.getSpans({ start: end + 1, end: end + 1 }).length === 0) { 984 this.visibilityValue = Visibility.None 985 } else { 986 this.visibilityValue = Visibility.Visible 987 } 988 } 989 } 990 let sysBoard = pasteboard.getSystemPasteboard() 991 if (sysBoard && sysBoard.hasDataSync()) { 992 this.pasteEnable = true 993 } else { 994 this.pasteEnable = false 995 } 996 } 997 998 build() { 999 Column() { 1000 Column() { 1001 RichEditor(this.options) 1002 .onReady(() => { 1003 this.controller.addTextSpan(this.message, { style: { fontColor: Color.Orange, fontSize: 30 } }) 1004 }) 1005 .onSelect((value: RichEditorSelection) => { 1006 if (value.selection[0] == -1 && value.selection[1] == -1) { 1007 return 1008 } 1009 this.start = value.selection[0] 1010 this.end = value.selection[1] 1011 }) 1012 .bindSelectionMenu(RichEditorSpanType.TEXT, this.panel, ResponseType.LongPress, { onDisappear: () => { 1013 this.sliderShow = false 1014 }}) 1015 .bindSelectionMenu(RichEditorSpanType.TEXT, this.panel, ResponseType.RightClick, { onDisappear: () => { 1016 this.sliderShow = false 1017 }}) 1018 .borderWidth(1) 1019 .borderColor(Color.Red) 1020 .width(200) 1021 .height(200) 1022 }.width('100%').backgroundColor(Color.White) 1023 }.height('100%') 1024 } 1025 1026 PushDataToPasteboard(richEditorSelection: RichEditorSelection) { 1027 let sysBoard = pasteboard.getSystemPasteboard() 1028 let pasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, '') 1029 if (richEditorSelection.spans && richEditorSelection.spans.length > 0) { 1030 let count = richEditorSelection.spans.length 1031 for (let i = count - 1; i >= 0; i--) { 1032 let item = richEditorSelection.spans[i] 1033 if ((item as RichEditorTextSpanResult)?.textStyle) { 1034 let span = item as RichEditorTextSpanResult 1035 let style = span.textStyle 1036 let data = pasteboard.createRecord(pasteboard.MIMETYPE_TEXT_PLAIN, span.value.substring(span.offsetInSpan[0], span.offsetInSpan[1])) 1037 let prop = pasteData.getProperty() 1038 let temp: Record<string, Object> = { 1039 'color': style.fontColor, 1040 'size': style.fontSize, 1041 'style': style.fontStyle, 1042 'weight': this.fontWeightTable[style.fontWeight], 1043 'fontFamily': style.fontFamily, 1044 'decorationType': style.decoration.type, 1045 'decorationColor': style.decoration.color 1046 } 1047 prop.additions[i] = temp; 1048 pasteData.addRecord(data) 1049 pasteData.setProperty(prop) 1050 } 1051 } 1052 } 1053 sysBoard.clearData() 1054 sysBoard.setData(pasteData).then(() => { 1055 console.info('SelectionMenu copy option, Succeeded in setting PasteData.'); 1056 this.pasteEnable = true; 1057 }).catch((err: BusinessError) => { 1058 console.error('SelectionMenu copy option, Failed to set PasteData. Cause:' + err.message); 1059 }) 1060 } 1061 1062 PopDataFromPasteboard(richEditorSelection: RichEditorSelection) { 1063 let start = richEditorSelection.selection[0] 1064 let end = richEditorSelection.selection[1] 1065 if (start == end && this.controller) { 1066 start = this.controller.getCaretOffset() 1067 end = this.controller.getCaretOffset() 1068 } 1069 let moveOffset = 0 1070 let sysBoard = pasteboard.getSystemPasteboard() 1071 sysBoard.getData((err, data) => { 1072 if (err) { 1073 return 1074 } 1075 let count = data.getRecordCount() 1076 for (let i = 0; i < count; i++) { 1077 const element = data.getRecord(i); 1078 let tex: RichEditorTextStyle = { 1079 fontSize: 16, 1080 fontColor: Color.Black, 1081 fontWeight: FontWeight.Normal, 1082 fontFamily: "HarmonyOS Sans", 1083 fontStyle: FontStyle.Normal, 1084 decoration: { type: TextDecorationType.None, color: "#FF000000" } 1085 } 1086 if (data.getProperty() && data.getProperty().additions[i]) { 1087 const tmp = data.getProperty().additions[i] as Record<string, Object | undefined>; 1088 if (tmp.color) { 1089 tex.fontColor = tmp.color as ResourceColor; 1090 } 1091 if (tmp.size) { 1092 tex.fontSize = tmp.size as Length | number; 1093 } 1094 if (tmp.style) { 1095 tex.fontStyle = tmp.style as FontStyle; 1096 } 1097 if (tmp.weight) { 1098 tex.fontWeight = tmp.weight as number | FontWeight | string; 1099 } 1100 if (tmp.fontFamily) { 1101 tex.fontFamily = tmp.fontFamily as ResourceStr; 1102 } 1103 if (tmp.decorationType && tex.decoration) { 1104 tex.decoration.type = tmp.decorationType as TextDecorationType; 1105 } 1106 if (tmp.decorationColor && tex.decoration) { 1107 tex.decoration.color = tmp.decorationColor as ResourceColor; 1108 } 1109 if (tex.decoration) { 1110 tex.decoration = { type: tex.decoration.type, color: tex.decoration.color } 1111 } 1112 } 1113 if (element && element.plainText && element.mimeType === pasteboard.MIMETYPE_TEXT_PLAIN && this.controller) { 1114 this.controller.addTextSpan(element.plainText, 1115 { 1116 style: tex, 1117 offset: start + moveOffset 1118 } 1119 ) 1120 moveOffset += element.plainText.length 1121 } 1122 } 1123 if (this.controller) { 1124 this.controller.setCaretOffset(start + moveOffset) 1125 this.controller.closeSelectionMenu() 1126 } 1127 if (start != end && this.controller) { 1128 this.controller.deleteSpans({ start: start + moveOffset, end: end + moveOffset }) 1129 } 1130 }) 1131 } 1132 1133 @Builder 1134 panel() { 1135 Column() { 1136 this.iconPanel() 1137 if (!this.sliderShow) { 1138 this.SystemMenu() 1139 } else { 1140 this.sliderPanel() 1141 } 1142 }.width(256) 1143 } 1144 1145 @Builder iconPanel() { 1146 Column() { 1147 Row({ space: 2 }) { 1148 ForEach(this.iconArr, (item:Resource, index ?: number) => { 1149 Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { 1150 Image(item).fillColor(this.theme.imageFillColor).width(24).height(24).focusable(true).draggable(false) 1151 } 1152 .borderRadius(this.theme.iconBorderRadius) 1153 .width(this.theme.buttonSize) 1154 .height(this.theme.buttonSize) 1155 .onClick(() => { 1156 if (index as number == 0) { 1157 this.sliderShow = false 1158 if (this.controller) { 1159 let selection = this.controller.getSelection(); 1160 let spans = selection.spans 1161 spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 1162 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 1163 let span = item as RichEditorTextSpanResult 1164 this.textStyle = span.textStyle 1165 let start = span.offsetInSpan[0] 1166 let end = span.offsetInSpan[1] 1167 let offset = span.spanPosition.spanRange[0] 1168 if (this.textStyle.fontWeight != 11) { 1169 this.textStyle.fontWeight = FontWeight.Bolder 1170 } else { 1171 this.textStyle.fontWeight = FontWeight.Normal 1172 } 1173 this.controller.updateSpanStyle({ 1174 start: offset + start, 1175 end: offset + end, 1176 textStyle: this.textStyle 1177 }) 1178 } 1179 }) 1180 } 1181 } else if (index as number == 1) { 1182 this.sliderShow = false 1183 if (this.controller) { 1184 let selection = this.controller.getSelection(); 1185 let spans = selection.spans 1186 spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 1187 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 1188 let span = item as RichEditorTextSpanResult 1189 this.textStyle = span.textStyle 1190 let start = span.offsetInSpan[0] 1191 let end = span.offsetInSpan[1] 1192 let offset = span.spanPosition.spanRange[0] 1193 if (this.textStyle.fontStyle == FontStyle.Italic) { 1194 this.textStyle.fontStyle = FontStyle.Normal 1195 } else { 1196 this.textStyle.fontStyle = FontStyle.Italic 1197 } 1198 this.controller.updateSpanStyle({ 1199 start: offset + start, 1200 end: offset + end, 1201 textStyle: this.textStyle 1202 }) 1203 } 1204 }) 1205 } 1206 } else if (index as number == 2) { 1207 this.sliderShow = false 1208 if (this.controller) { 1209 let selection = this.controller.getSelection(); 1210 let spans = selection.spans 1211 spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 1212 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 1213 let span = item as RichEditorTextSpanResult 1214 this.textStyle = span.textStyle 1215 let start = span.offsetInSpan[0] 1216 let end = span.offsetInSpan[1] 1217 let offset = span.spanPosition.spanRange[0] 1218 if (this.textStyle.decoration) { 1219 if (this.textStyle.decoration.type == TextDecorationType.Underline) { 1220 this.textStyle.decoration.type = TextDecorationType.None 1221 } else { 1222 this.textStyle.decoration.type = TextDecorationType.Underline 1223 } 1224 } else { 1225 this.textStyle.decoration = { type: TextDecorationType.Underline, color: Color.Black } 1226 } 1227 this.controller.updateSpanStyle({ 1228 start: offset + start, 1229 end: offset + end, 1230 textStyle: this.textStyle 1231 }) 1232 } 1233 }) 1234 } 1235 } else if (index as number == 3) { 1236 this.sliderShow = !this.sliderShow 1237 } else if (index as number == 4) { 1238 this.sliderShow = false 1239 if (this.controller) { 1240 let selection = this.controller.getSelection(); 1241 let spans = selection.spans 1242 spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 1243 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 1244 let span = item as RichEditorTextSpanResult 1245 this.textStyle = span.textStyle 1246 let start = span.offsetInSpan[0] 1247 let end = span.offsetInSpan[1] 1248 let offset = span.spanPosition.spanRange[0] 1249 if (this.textStyle.fontColor == Color.Orange || this.textStyle.fontColor == '#FFFFA500') { 1250 this.textStyle.fontColor = Color.Black 1251 } else { 1252 this.textStyle.fontColor = Color.Orange 1253 } 1254 this.controller.updateSpanStyle({ 1255 start: offset + start, 1256 end: offset + end, 1257 textStyle: this.textStyle 1258 }) 1259 } 1260 }) 1261 } 1262 } 1263 }) 1264 .onTouch((event?: TouchEvent | undefined) => { 1265 if(event != undefined){ 1266 if (event.type === TouchType.Down) { 1267 this.iconBgColor[index as number] = $r('sys.color.ohos_id_color_click_effect') 1268 } 1269 if (event.type === TouchType.Up) { 1270 this.iconBgColor[index as number] = this.colorTransparent 1271 } 1272 } 1273 }) 1274 .onHover((isHover?: boolean, event?: HoverEvent) => { 1275 this.iconBgColor.forEach((icon:ResourceColor, index1) => { 1276 this.iconBgColor[index1] = this.colorTransparent 1277 }) 1278 if(isHover != undefined) { 1279 this.iconBgColor[index as number] = $r('sys.color.ohos_id_color_hover') 1280 } 1281 }) 1282 .backgroundColor(this.iconBgColor[index as number]) 1283 }) 1284 } 1285 } 1286 .clip(true) 1287 .width(this.theme.defaultMenuWidth) 1288 .padding(this.theme.expandedOptionPadding) 1289 .borderRadius(this.theme.containerBorderRadius) 1290 .margin({ bottom: this.theme.menuSpacing }) 1291 .backgroundColor(this.theme.backGroundColor) 1292 .shadow(this.theme.iconPanelShadowStyle) 1293 } 1294 1295 @Builder 1296 SystemMenu() { 1297 Column() { 1298 Menu() { 1299 if (this.controller) { 1300 MenuItemGroup() { 1301 MenuItem({ startIcon: this.theme.cutIcon, content: "剪切", labelInfo: "Ctrl+X" }) 1302 .onClick(() => { 1303 if (!this.controller) { 1304 return 1305 } 1306 let richEditorSelection = this.controller.getSelection() 1307 this.PushDataToPasteboard(richEditorSelection); 1308 this.controller.deleteSpans({ 1309 start: richEditorSelection.selection[0], 1310 end: richEditorSelection.selection[1] 1311 }) 1312 }) 1313 MenuItem({ startIcon: this.theme.copyIcon, content: "复制", labelInfo: "Ctrl+C" }) 1314 .onClick(() => { 1315 if (!this.controller) { 1316 return 1317 } 1318 let richEditorSelection = this.controller.getSelection() 1319 this.PushDataToPasteboard(richEditorSelection); 1320 this.controller.closeSelectionMenu() 1321 }) 1322 MenuItem({ startIcon: this.theme.pasteIcon, content: "粘贴", labelInfo: "Ctrl+V" }) 1323 .enabled(this.pasteEnable) 1324 .onClick(() => { 1325 if (!this.controller) { 1326 return 1327 } 1328 let richEditorSelection = this.controller.getSelection() 1329 this.PopDataFromPasteboard(richEditorSelection) 1330 }) 1331 MenuItem({ startIcon: this.theme.selectAllIcon, content: "全选", labelInfo: "Ctrl+A" }) 1332 .visibility(this.visibilityValue) 1333 .onClick(() => { 1334 if (!this.controller) { 1335 return 1336 } 1337 this.controller.setSelection(-1, -1) 1338 this.visibilityValue = Visibility.None 1339 }) 1340 MenuItem({ startIcon: this.theme.shareIcon, content: "分享", labelInfo: "" }) 1341 .enabled(false) 1342 MenuItem({ startIcon: this.theme.translateIcon, content: "翻译", labelInfo: "" }) 1343 .enabled(false) 1344 MenuItem({ startIcon: this.theme.searchIcon, content: "搜索", labelInfo: "" }) 1345 .enabled(false) 1346 } 1347 } 1348 } 1349 .onVisibleAreaChange([0.0, 1.0], () => { 1350 if (!this.controller) { 1351 return 1352 } 1353 let richEditorSelection = this.controller.getSelection() 1354 let start = richEditorSelection.selection[0] 1355 let end = richEditorSelection.selection[1] 1356 if (start === 0 && this.controller.getSpans({ start: end + 1, end: end + 1 }).length === 0) { 1357 this.visibilityValue = Visibility.None 1358 } else { 1359 this.visibilityValue = Visibility.Visible 1360 } 1361 }) 1362 .radius(this.theme.containerBorderRadius) 1363 .clip(true) 1364 .backgroundColor(Color.White) 1365 .width(this.theme.defaultMenuWidth) 1366 } 1367 .width(this.theme.defaultMenuWidth) 1368 } 1369 1370 @Builder sliderPanel() { 1371 Column() { 1372 Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { 1373 Text('A').fontSize(15) 1374 Slider({ value: this.textSize, step: 10, style: SliderStyle.InSet }) 1375 .width(210) 1376 .onChange((value: number, mode: SliderChangeMode) => { 1377 if (this.controller) { 1378 let selection = this.controller.getSelection(); 1379 if (mode == SliderChangeMode.End) { 1380 if (this.textSize == undefined) { 1381 this.textSize = 0 1382 } 1383 let spans = selection.spans 1384 spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 1385 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 1386 this.textSize = Math.max(this.textSize, (item as RichEditorTextSpanResult).textStyle.fontSize) 1387 } 1388 }) 1389 } 1390 if (mode == SliderChangeMode.Moving || mode == SliderChangeMode.Click) { 1391 this.start = selection.selection[0] 1392 this.end = selection.selection[1] 1393 this.textSize = value 1394 this.controller.updateSpanStyle({ 1395 start: this.start, 1396 end: this.end, 1397 textStyle: { fontSize: this.textSize } 1398 }) 1399 } 1400 } 1401 }) 1402 Text('A').fontSize(20).fontWeight(FontWeight.Medium) 1403 }.borderRadius(this.theme.containerBorderRadius) 1404 } 1405 .shadow(ShadowStyle.OUTER_DEFAULT_MD) 1406 .backgroundColor(Color.White) 1407 .borderRadius(this.theme.containerBorderRadius) 1408 .padding(15) 1409 .height(48) 1410 } 1411} 1412``` 1413> **说明:** 1414> 1415> 系统暂未预置加粗、斜体等图标,示例代码使用系统默认图标,开发者使用时需自行替换iconArr中的资源。 1416 1417 1418 1419### 示例4 1420 1421```ts 1422// xxx.ets 1423@Entry 1424@Component 1425struct Index { 1426 controller: RichEditorController = new RichEditorController(); 1427 options: RichEditorOptions = { controller: this.controller }; 1428 private start: number = -1; 1429 private end: number = -1; 1430 @State message: string = "[-1, -1]" 1431 @State content: string = "" 1432 @State paddingVal: number = 5 1433 @State borderRad: number = 4 1434 1435 build() { 1436 Column() { 1437 Column() { 1438 Text("selection range:").width("100%") 1439 Text() { 1440 Span(this.message) 1441 }.width("100%") 1442 Text("selection content:").width("100%") 1443 Text() { 1444 Span(this.content) 1445 }.width("100%") 1446 } 1447 .borderWidth(1) 1448 .borderColor(Color.Red) 1449 .width("100%") 1450 .height("20%") 1451 1452 Row() { 1453 Button("updateSpanStyle1") 1454 .fontSize(12) 1455 .onClick(() => { 1456 this.controller.updateSpanStyle({ 1457 start: this.start, 1458 textStyle: 1459 { 1460 fontWeight: FontWeight.Bolder 1461 }, 1462 imageStyle: { 1463 size: ["80px", "80px"], 1464 layoutStyle: { 1465 borderRadius: undefined, 1466 margin: undefined 1467 } 1468 } 1469 }) 1470 }) 1471 1472 Button("updateSpanStyle2") 1473 .fontSize(12) 1474 .onClick(() => { 1475 this.controller.updateSpanStyle({ 1476 start: this.start, 1477 textStyle: 1478 { 1479 fontWeight: FontWeight.Bolder 1480 }, 1481 imageStyle: { 1482 size: ["70px", "70px"], 1483 layoutStyle: { 1484 borderRadius: { topLeft: '100px', topRight: '20px', bottomLeft: '100px', bottomRight: '20px' }, 1485 margin: { left: '30px', top: '20px', right: '20px', bottom: '20px' } 1486 } 1487 } 1488 }) 1489 }) 1490 1491 Button("updateSpanStyle3") 1492 .fontSize(12) 1493 .onClick(() => { 1494 this.controller.updateSpanStyle({ 1495 start: this.start, 1496 textStyle: 1497 { 1498 fontWeight: FontWeight.Bolder 1499 }, 1500 imageStyle: { 1501 size: ["60px", "60px"], 1502 layoutStyle: { 1503 borderRadius: '-10px', 1504 margin: '-10px' 1505 } 1506 } 1507 }) 1508 }) 1509 } 1510 .borderWidth(1) 1511 .borderColor(Color.Red) 1512 .width("100%") 1513 .height("10%") 1514 1515 Row() { 1516 Button('addImageSpan1') 1517 .fontSize(12) 1518 .onClick(() => { 1519 this.controller.addImageSpan($r('app.media.app_icon'), { 1520 imageStyle: { 1521 size: ["80px", "80px"], 1522 layoutStyle: { 1523 borderRadius: '50px', 1524 margin: '40px' 1525 } 1526 } 1527 }) 1528 }) 1529 1530 Button('addImageSpan2') 1531 .fontSize(12) 1532 .onClick(() => { 1533 this.controller.addImageSpan($r('app.media.app_icon'), { 1534 imageStyle: { 1535 size: ["100px", "100px"], 1536 verticalAlign: ImageSpanAlignment.BOTTOM, 1537 layoutStyle: { 1538 borderRadius: undefined, 1539 margin: undefined 1540 } 1541 } 1542 }) 1543 }) 1544 1545 Button('addImageSpan3') 1546 .fontSize(12) 1547 .onClick(() => { 1548 this.controller.addImageSpan($r('app.media.app_icon'), { 1549 imageStyle: { 1550 size: ["60px", "60px"], 1551 verticalAlign: ImageSpanAlignment.BOTTOM, 1552 layoutStyle: { 1553 borderRadius: { topLeft: '10px', topRight: '20px', bottomLeft: '30px', bottomRight: '40px' }, 1554 margin: { left: '10px', top: '20px', right: '30px', bottom: '40px' } 1555 } 1556 } 1557 }) 1558 }) 1559 } 1560 .borderWidth(1) 1561 .borderColor(Color.Red) 1562 .width("100%") 1563 .height("10%") 1564 1565 Column() { 1566 RichEditor(this.options) 1567 .onReady(() => { 1568 this.controller.addTextSpan("0123456789", 1569 { 1570 style: 1571 { 1572 fontColor: Color.Orange, 1573 fontSize: 30 1574 } 1575 }) 1576 1577 this.controller.addImageSpan($r("app.media.app_icon"), 1578 { 1579 imageStyle: 1580 { 1581 size: ["60px", "60px"], 1582 verticalAlign: ImageSpanAlignment.BOTTOM, 1583 layoutStyle: { 1584 borderRadius: { topLeft: '10px', topRight: '20px', bottomLeft: '30px', bottomRight: '40px' }, 1585 margin: { left: '10px', top: '20px', right: '30px', bottom: '40px' } 1586 } 1587 } 1588 }) 1589 1590 this.controller.addTextSpan("0123456789", 1591 { 1592 style: 1593 { 1594 fontColor: Color.Black, 1595 fontSize: 30 1596 } 1597 }) 1598 }) 1599 .onSelect((value: RichEditorSelection) => { 1600 this.start = value.selection[0]; 1601 this.end = value.selection[1]; 1602 this.message = "[" + this.start + ", " + this.end + "]" 1603 }) 1604 .aboutToIMEInput((value: RichEditorInsertValue) => { 1605 console.log("---------------------- aboutToIMEInput ----------------------") 1606 console.log("insertOffset:" + value.insertOffset) 1607 console.log("insertValue:" + value.insertValue) 1608 return true; 1609 }) 1610 .onIMEInputComplete((value: RichEditorTextSpanResult) => { 1611 console.log("---------------------- onIMEInputComplete ---------------------") 1612 console.log("spanIndex:" + value.spanPosition.spanIndex) 1613 console.log("spanRange:[" + value.spanPosition.spanRange[0] + "," + value.spanPosition.spanRange[1] + "]") 1614 console.log("offsetInSpan:[" + value.offsetInSpan[0] + "," + value.offsetInSpan[1] + "]") 1615 console.log("value:" + value.value) 1616 }) 1617 .aboutToDelete((value: RichEditorDeleteValue) => { 1618 console.log("---------------------- aboutToDelete --------------------------") 1619 console.log("offset:" + value.offset) 1620 console.log("direction:" + value.direction) 1621 console.log("length:" + value.length) 1622 value.richEditorDeleteSpans.forEach(item => { 1623 console.log("---------------------- item --------------------------") 1624 console.log("spanIndex:" + item.spanPosition.spanIndex) 1625 console.log("spanRange:[" + item.spanPosition.spanRange[0] + "," + item.spanPosition.spanRange[1] + "]") 1626 console.log("offsetInSpan:[" + item.offsetInSpan[0] + "," + item.offsetInSpan[1] + "]") 1627 if (typeof (item as RichEditorImageSpanResult)['imageStyle'] != 'undefined') { 1628 console.log("image:" + (item as RichEditorImageSpanResult).valueResourceStr) 1629 } else { 1630 console.log("text:" + (item as RichEditorTextSpanResult).value) 1631 } 1632 }) 1633 return true; 1634 }) 1635 .onDeleteComplete(() => { 1636 console.log("---------------------- onDeleteComplete ------------------------") 1637 }) 1638 .borderWidth(1) 1639 .borderColor(Color.Green) 1640 .width("100%") 1641 .height('80.00%') 1642 } 1643 .borderWidth(1) 1644 .borderColor(Color.Red) 1645 .width("100%") 1646 .height("70%") 1647 } 1648 } 1649} 1650``` 1651 1652 1653### 示例5 1654 1655```ts 1656// xxx.ets 1657@Entry 1658@Component 1659struct Index { 1660 controller: RichEditorController = new RichEditorController() 1661 options: RichEditorOptions = { controller: this.controller }; 1662 @State textFlag: string = "TextFlag"; 1663 1664 build() { 1665 Column() { 1666 Column() { 1667 Text(this.textFlag) 1668 .copyOption(CopyOptions.InApp) 1669 .fontSize(50) 1670 } 1671 Divider() 1672 Column() { 1673 RichEditor(this.options) 1674 .onReady(() => { 1675 this.controller.addTextSpan('Area1\n', { 1676 style: 1677 { 1678 fontColor: Color.Orange, 1679 fontSize: 50 1680 }, 1681 gesture: 1682 { 1683 onClick: () => { 1684 this.textFlag = "Area1 is onClick." 1685 }, 1686 onLongPress: () => { 1687 this.textFlag = "Area1 is onLongPress." 1688 } 1689 } 1690 }) 1691 1692 this.controller.addTextSpan('Area2\n', { 1693 style: 1694 { 1695 fontColor: Color.Blue, 1696 fontSize: 50 1697 }, 1698 gesture: 1699 { 1700 onClick: () => { 1701 this.textFlag = "Area2 is onClick." 1702 }, 1703 onLongPress: () => { 1704 this.textFlag = "Area2 is onLongPress." 1705 } 1706 } 1707 }) 1708 1709 this.controller.addImageSpan($r("app.media.icon"), 1710 { 1711 imageStyle: 1712 { 1713 size: ["100px", "100px"], 1714 layoutStyle: { 1715 margin: 5, 1716 borderRadius: 15 1717 } 1718 }, 1719 gesture: 1720 { 1721 onClick: () => { 1722 this.textFlag = "ImageSpan is onClick." 1723 }, 1724 onLongPress: () => { 1725 this.textFlag = "ImageSpan is onLongPress." 1726 } 1727 } 1728 }) 1729 }) 1730 } 1731 .borderWidth(1) 1732 .borderColor(Color.Red) 1733 .width("100%") 1734 .height("70%") 1735 } 1736 } 1737} 1738``` 1739 1740 1741### 示例6 1742 1743```ts 1744// xxx.ets 1745@Entry 1746@Component 1747struct Index { 1748 controller: RichEditorController = new RichEditorController(); 1749 private spanParagraphs: RichEditorParagraphResult[] = []; 1750 1751 build() { 1752 Column() { 1753 RichEditor({ controller: this.controller }) 1754 .onReady(() => { 1755 this.controller.addTextSpan("0123456789\n", { 1756 style: { 1757 fontColor: Color.Pink, 1758 fontSize: "32", 1759 }, 1760 paragraphStyle: { 1761 textAlign: TextAlign.Start, 1762 leadingMargin: 16 1763 } 1764 }) 1765 this.controller.addTextSpan("0123456789") 1766 }) 1767 .width("80%") 1768 .height("30%") 1769 .border({ width: 1, radius: 5 }) 1770 .draggable(false) 1771 1772 Column({ space: 5 }) { 1773 Button("段落左对齐").onClick(() => { 1774 this.controller.updateParagraphStyle({ start: -1, end: -1, 1775 style: { 1776 textAlign: TextAlign.Start, 1777 } 1778 }) 1779 }) 1780 1781 Button("段落右对齐").onClick(() => { 1782 this.controller.updateParagraphStyle({ start: -1, end: -1, 1783 style: { 1784 textAlign: TextAlign.End, 1785 } 1786 }) 1787 }) 1788 1789 Button("段落居中").onClick(() => { 1790 this.controller.updateParagraphStyle({ start: -1, end: -1, 1791 style: { 1792 textAlign: TextAlign.Center, 1793 } 1794 }) 1795 }) 1796 Divider() 1797 Button("getParagraphs").onClick(() => { 1798 this.spanParagraphs = this.controller.getParagraphs({ start: -1, end: -1 }) 1799 console.log("RichEditor getParagraphs:" + JSON.stringify(this.spanParagraphs)) 1800 }) 1801 1802 Button("UpdateSpanStyle1").onClick(() => { 1803 this.controller.updateSpanStyle({ start: -1, end: -1, 1804 textStyle: { 1805 fontColor: Color.Brown, 1806 fontSize: 20 1807 } 1808 }) 1809 }) 1810 1811 Button("UpdateSpanStyle2").onClick(() => { 1812 this.controller.updateSpanStyle({ start: -1, end: -1, 1813 textStyle: { 1814 fontColor: Color.Green, 1815 fontSize: 30 1816 } 1817 }) 1818 }) 1819 } 1820 } 1821 } 1822} 1823``` 1824 1825 1826### 示例7 1827 1828```ts 1829// xxx.ets 1830import font from '@ohos.font' 1831const canvasWidth = 1000 1832const canvasHeight = 100 1833const Indentation = 40 1834class LeadingMarginCreator { 1835 private settings: RenderingContextSettings = new RenderingContextSettings(true) 1836 private offscreenCanvas: OffscreenCanvas = new OffscreenCanvas(canvasWidth, canvasHeight) 1837 private offContext: OffscreenCanvasRenderingContext2D = this.offscreenCanvas.getContext("2d", this.settings) 1838 public static instance: LeadingMarginCreator = new LeadingMarginCreator() 1839 1840 // 获得字体字号级别,分别是从0到4级 1841 public getFontSizeLevel(fontSize: number) { 1842 const fontScaled: number = Number(fontSize) / 16 1843 1844 enum FontSizeScaleThreshold { 1845 SMALL = 0.9, 1846 NORMAL = 1.1, 1847 LEVEL_1_LARGE = 1.2, 1848 LEVEL_2_LARGE = 1.4, 1849 LEVEL_3_LARGE = 1.5 1850 } 1851 1852 let fontSizeLevel: number = 1 1853 1854 if (fontScaled < FontSizeScaleThreshold.SMALL) { 1855 fontSizeLevel = 0 1856 } else if (fontScaled < FontSizeScaleThreshold.NORMAL) { 1857 fontSizeLevel = 1 1858 } else if (fontScaled < FontSizeScaleThreshold.LEVEL_1_LARGE) { 1859 fontSizeLevel = 2 1860 } else if (fontScaled < FontSizeScaleThreshold.LEVEL_2_LARGE) { 1861 fontSizeLevel = 3 1862 } else if (fontScaled < FontSizeScaleThreshold.LEVEL_3_LARGE) { 1863 fontSizeLevel = 4 1864 } else { 1865 fontSizeLevel = 1 1866 } 1867 1868 return fontSizeLevel 1869 } 1870 // 获得字体字号级别,分别是从0到4级 1871 public getmarginLevel(Width: number) { 1872 let marginlevel: number = 1 1873 if (Width == 40) { 1874 marginlevel = 2.0 1875 } else if (Width == 80) { 1876 marginlevel = 1.0 1877 } else if (Width == 120) { 1878 marginlevel = 2/3 1879 } else if (Width == 160) { 1880 marginlevel = 0.5 1881 } else if (Width == 200) { 1882 marginlevel = 0.4 1883 } 1884 return marginlevel 1885 } 1886 1887 public genStrMark(fontSize: number, str: string): PixelMap { 1888 this.offContext = this.offscreenCanvas.getContext("2d", this.settings) 1889 this.clearCanvas() 1890 this.offContext.font = fontSize + 'vp sans-serif' 1891 this.offContext.fillText(str + '.', 0, fontSize * 0.9) 1892 return this.offContext.getPixelMap(0, 0, fontSize * (str.length + 1) / 1.75, fontSize) 1893 } 1894 1895 public genSquareMark(fontSize: number): PixelMap { 1896 this.offContext = this.offscreenCanvas.getContext("2d", this.settings) 1897 this.clearCanvas() 1898 const coordinate = fontSize * (1 - 1 / 1.5) / 2 1899 const sideLength = fontSize / 1.5 1900 this.offContext.fillRect(coordinate, coordinate, sideLength, sideLength) 1901 return this.offContext.getPixelMap(0, 0, fontSize, fontSize) 1902 } 1903 1904 // 生成圆圈符号 1905 public genCircleMark(fontSize: number, width: number, level?: number ): PixelMap { 1906 const indentLevel = level ?? 1 1907 const offsetLevel = [22, 28, 32, 34, 38] 1908 const fontSizeLevel = this.getFontSizeLevel(fontSize) 1909 const marginlevel = this.getmarginLevel(width) 1910 const newOffContext: OffscreenCanvasRenderingContext2D = this.offscreenCanvas.getContext("2d", this.settings) 1911 const centerCoordinate = 50 1912 const radius = 10 1913 this.clearCanvas() 1914 newOffContext.ellipse(100 * (indentLevel + 1) - centerCoordinate * marginlevel, offsetLevel[fontSizeLevel], radius * marginlevel, radius, 0, 0, 2 * Math.PI) 1915 newOffContext.fillStyle = '66FF0000' 1916 newOffContext.fill() 1917 return newOffContext.getPixelMap(0, 0, 100 + 100 * indentLevel, 100) 1918 } 1919 1920 private clearCanvas() { 1921 this.offContext.clearRect(0, 0, canvasWidth, canvasHeight) 1922 } 1923} 1924 1925@Entry 1926@Component 1927struct Index { 1928 controller: RichEditorController = new RichEditorController() 1929 options: RichEditorOptions = { controller: this.controller } 1930 private leadingMarkCreatorInstance = LeadingMarginCreator.instance 1931 private fontNameRawFile: string = 'MiSans-Bold' 1932 @State fs: number = 30 1933 @State cl: number = Color.Black 1934 private leftMargin: Dimension = 0 1935 private richEditorTextStyle: RichEditorTextStyle = {} 1936 1937 aboutToAppear() { 1938 font.registerFont({ 1939 familyName: 'MiSans-Bold', 1940 familySrc: '/font/MiSans-Bold.ttf' 1941 }) 1942 } 1943 1944 build() { 1945 Scroll() { 1946 Column() { 1947 RichEditor(this.options) 1948 .onReady(() => { 1949 this.controller.addTextSpan("0123456789\n", 1950 { 1951 style: 1952 { 1953 fontWeight: 'medium', 1954 fontFamily: this.fontNameRawFile, 1955 fontColor: Color.Red, 1956 fontSize: 50, 1957 fontStyle: FontStyle.Italic, 1958 decoration: { type: TextDecorationType.Underline, color: Color.Green } 1959 } 1960 }) 1961 1962 this.controller.addTextSpan("abcdefg", 1963 { 1964 style: 1965 { 1966 fontWeight: FontWeight.Lighter, 1967 fontFamily: 'HarmonyOS Sans', 1968 fontColor: 'rgba(0,128,0,0.5)', 1969 fontSize: 30, 1970 fontStyle: FontStyle.Normal, 1971 decoration: { type: TextDecorationType.Overline, color: 'rgba(169, 26, 246, 0.50)' } 1972 } 1973 }) 1974 }) 1975 .borderWidth(1) 1976 .borderColor(Color.Green) 1977 .width("100%") 1978 .height("50%") 1979 1980 Row({ space: 5 }) { 1981 Button('setTypingStyle1') 1982 .fontSize(10) 1983 .onClick(() => { 1984 this.controller.setTypingStyle( 1985 { 1986 fontWeight: 'medium', 1987 fontFamily: this.fontNameRawFile, 1988 fontColor: Color.Blue, 1989 fontSize: 50, 1990 fontStyle: FontStyle.Italic, 1991 decoration: { type: TextDecorationType.Underline, color: Color.Green } 1992 }) 1993 }) 1994 1995 Button('setTypingStyle2') 1996 .fontSize(10) 1997 .onClick(() => { 1998 this.controller.setTypingStyle( 1999 { 2000 fontWeight: FontWeight.Lighter, 2001 fontFamily: 'HarmonyOS Sans', 2002 fontColor: Color.Green, 2003 fontSize: '30', 2004 fontStyle: FontStyle.Normal, 2005 decoration: { type: TextDecorationType.Overline, color: 'rgba(169, 26, 246, 0.50)' } 2006 }) 2007 }) 2008 } 2009 Divider() 2010 Button("getTypingStyle").onClick(() => { 2011 this.richEditorTextStyle = this.controller.getTypingStyle() 2012 console.log("RichEditor getTypingStyle:" + JSON.stringify(this.richEditorTextStyle)) 2013 }) 2014 Divider() 2015 Row({ space: 5 }) { 2016 Button("向右列表缩进").onClick(() => { 2017 let margin = Number(this.leftMargin) 2018 if (margin < 200) { 2019 margin += Indentation 2020 this.leftMargin = margin 2021 } 2022 this.controller.updateParagraphStyle({ 2023 start: -10, 2024 end: -10, 2025 style: { 2026 leadingMargin : { 2027 pixelMap : this.leadingMarkCreatorInstance.genCircleMark(100, margin, 1), 2028 size: [margin, 40] 2029 } 2030 } 2031 }) 2032 }) 2033 2034 Button("向左列表缩进").onClick(() => { 2035 let margin = Number(this.leftMargin) 2036 if (margin > 0) { 2037 margin -= Indentation 2038 this.leftMargin = margin 2039 } 2040 this.controller.updateParagraphStyle({ 2041 start: -10, 2042 end: -10, 2043 style: { 2044 leadingMargin : { 2045 pixelMap : this.leadingMarkCreatorInstance.genCircleMark(100, margin, 1), 2046 size: [margin, 40] 2047 } 2048 } 2049 }) 2050 }) 2051 } 2052 Divider() 2053 Row({ space: 5 }) { 2054 Button("向右空白缩进").onClick(() => { 2055 let margin = Number(this.leftMargin) 2056 if (margin < 200) { 2057 margin += Indentation 2058 this.leftMargin = margin 2059 } 2060 this.controller.updateParagraphStyle({ 2061 start: -10, 2062 end: -10, 2063 style: { 2064 leadingMargin: margin 2065 } 2066 }) 2067 }) 2068 2069 Button("向左空白缩进").onClick(() => { 2070 let margin = Number(this.leftMargin) 2071 if (margin > 0) { 2072 margin -= Indentation 2073 this.leftMargin = margin 2074 } 2075 this.controller.updateParagraphStyle({ 2076 start: -10, 2077 end: -10, 2078 style: { 2079 leadingMargin: margin 2080 } 2081 }) 2082 }) 2083 } 2084 }.borderWidth(1).borderColor(Color.Red) 2085 } 2086 } 2087} 2088``` 2089 2090 2091### 示例8 2092``` ts 2093@Entry 2094@Component 2095struct Index { 2096 controller: RichEditorController = new RichEditorController(); 2097 options: RichEditorOptions = { controller: this.controller }; 2098 private start: number = -1; 2099 private end: number = -1; 2100 @State message: string = "[-1, -1]" 2101 @State content: string = "" 2102 @State visable :number = 0; 2103 @State index:number = 0; 2104 @State offsetx: number = 0; 2105 @State textShadows : (ShadowOptions | Array<ShadowOptions> ) = 2106 [{ radius: 10, color: Color.Red, offsetX: 10, offsetY: 0 },{ radius: 10, color: Color.Black, offsetX: 20, offsetY: 0 }, 2107 { radius: 10, color: Color.Brown, offsetX: 30, offsetY: 0 },{ radius: 10, color: Color.Green, offsetX: 40, offsetY: 0 }, 2108 { radius: 10, color: Color.Yellow, offsetX: 100, offsetY: 0 }] 2109 @State textshadowOf : ShadowOptions[] = [] 2110 build() { 2111 Column() { 2112 Column() { 2113 Text("selection range:").width("100%") 2114 Text() { 2115 Span(this.message) 2116 }.width("100%") 2117 Text("selection content:").width("100%") 2118 Text() { 2119 Span(this.content) 2120 }.width("100%") 2121 } 2122 .borderWidth(1) 2123 .borderColor(Color.Red) 2124 .width("100%") 2125 .height("20%") 2126 Row() { 2127 Button("更新样式: 加粗 & 文本阴影").onClick(() => { 2128 this.controller.updateSpanStyle({ 2129 start: this.start, 2130 end: this.end, 2131 textStyle: 2132 { 2133 fontWeight: FontWeight.Bolder, 2134 textShadow: this.textShadows 2135 } 2136 }) 2137 }) 2138 } 2139 .borderWidth(1) 2140 .borderColor(Color.Red) 2141 .width("100%") 2142 .height("10%") 2143 Column() { 2144 RichEditor(this.options) 2145 .onReady(() => { 2146 this.controller.addTextSpan("0123456789", 2147 { 2148 style: 2149 { 2150 fontColor: Color.Orange, 2151 fontSize: 30, 2152 textShadow: { radius: 10, color: Color.Blue, offsetX: 10, offsetY: 0 } 2153 } 2154 }) 2155 }) 2156 .borderWidth(1) 2157 .borderColor(Color.Green) 2158 .width("100%") 2159 .height("30%") 2160 } 2161 .borderWidth(1) 2162 .borderColor(Color.Red) 2163 .width("100%") 2164 .height("70%") 2165 } 2166 } 2167} 2168``` 2169 2170 2171 2172### 示例9 2173``` ts 2174@Builder 2175function placeholderBuilder2() { 2176 Row({ space: 2 }) { 2177 Image($r("app.media.icon")).width(24).height(24).margin({ left: -5 }) 2178 Text('okokokok').fontSize(10) 2179 }.width('20%').height(50).padding(10).backgroundColor(Color.Red) 2180} 2181 2182// xxx.ets 2183@Entry 2184@Component 2185struct Index { 2186 controller: RichEditorController = new RichEditorController(); 2187 option: RichEditorOptions = { controller: this.controller }; 2188 private start: number = 2; 2189 private end: number = 4; 2190 @State message: string = "[-1, -1]" 2191 @State content: string = "" 2192 private my_offset: number | undefined = undefined 2193 private my_builder: CustomBuilder = undefined 2194 2195 @Builder 2196 placeholderBuilder() { 2197 Row({ space: 2 }) { 2198 Image($r("app.media.icon")).width(24).height(24).margin({ left: -5 }) 2199 Text('Custom Popup').fontSize(10) 2200 }.width(100).height(50).padding(5) 2201 } 2202 2203 @Builder 2204 placeholderBuilder3() { 2205 Text("hello").padding('20').borderWidth(1).width('100%') 2206 } 2207 2208 @Builder 2209 placeholderBuilder4() { 2210 Column() { 2211 Column({ space: 5 }) { 2212 Text('direction:Row').fontSize(9).fontColor(0xCCCCCC).width('90%') 2213 Flex({ direction: FlexDirection.Row }) { // 子组件在容器主抽上行布局 2214 Text('1').width('20%').height(50).backgroundColor(0xF5DEB3) 2215 Text('1').width('20%').height(50).backgroundColor(0xD2B48C) 2216 Text('1').width('20%').height(50).backgroundColor(0xF5DEB3) 2217 Text('1').width('20%').height(50).backgroundColor(0xD2B48C) 2218 } 2219 .height(70) 2220 .width('90%') 2221 .padding(10) 2222 .backgroundColor(0xAFEEEE) 2223 2224 Text('direction:RowReverse').fontSize(9).fontColor(0xCCCCCC).width('90%') 2225 Flex({ direction: FlexDirection.RowReverse }) { // 子组件在容器主抽上反向行布局 2226 Text('1').width('20%').height(50).backgroundColor(0xF5DEB3) 2227 Text('1').width('20%').height(50).backgroundColor(0xD2B48C) 2228 Text('1').width('20%').height(50).backgroundColor(0xF5DEB3) 2229 Text('1').width('20%').height(50).backgroundColor(0xD2B48C) 2230 } 2231 .height(70) 2232 .width('90%') 2233 .padding(10) 2234 .backgroundColor(0xAFEEEE) 2235 2236 Text('direction:Column').fontSize(9).fontColor(0xCCCCCC).width('90%') 2237 Flex({ direction: FlexDirection.Column }) { // 子组件在容器主抽上列布局 2238 Text('1').width('20%').height(40).backgroundColor(0xF5DEB3) 2239 Text('1').width('20%').height(40).backgroundColor(0xD2B48C) 2240 Text('1').width('20%').height(40).backgroundColor(0xF5DEB3) 2241 Text('1').width('20%').height(40).backgroundColor(0xD2B48C) 2242 } 2243 .height(160) 2244 .width('90%') 2245 .padding(10) 2246 .backgroundColor(0xAFEEEE) 2247 2248 Text('direction:ColumnReverse').fontSize(9).fontColor(0xCCCCCC).width('90%') 2249 Flex({ direction: FlexDirection.ColumnReverse }) { // 子组件在容器主抽上反向列布局 2250 Text('1').width('20%').height(40).backgroundColor(0xF5DEB3) 2251 Text('1').width('20%').height(40).backgroundColor(0xD2B48C) 2252 Text('1').width('20%').height(40).backgroundColor(0xF5DEB3) 2253 Text('1').width('20%').height(40).backgroundColor(0xD2B48C) 2254 } 2255 .height(160) 2256 .width('90%') 2257 .padding(10) 2258 .backgroundColor(0xAFEEEE) 2259 }.width('100%').margin({ top: 5 }) 2260 }.width('100%') 2261 } 2262 2263 @Builder 2264 MyMenu() { 2265 Menu() { 2266 MenuItem({ startIcon: $r("app.media.icon"), content: "菜单选项1" }) 2267 MenuItem({ startIcon: $r("app.media.icon"), content: "菜单选项2" }) 2268 .enabled(false) 2269 } 2270 } 2271 2272 build() { 2273 Column() { 2274 Column() { 2275 Text("selection range:").width("100%") 2276 Text() { 2277 Span(this.message) 2278 }.width("100%") 2279 2280 Text("selection content:").width("100%") 2281 Text() { 2282 Span(this.content) 2283 }.width("100%") 2284 } 2285 .borderWidth(1) 2286 .borderColor(Color.Red) 2287 .width("100%") 2288 .height("20%") 2289 2290 Row() { 2291 Button("获取选择内容 getSpans").onClick(() => { 2292 console.info('getSpans='+JSON.stringify(this.controller.getSpans({ start:1, end:5 }))) 2293 console.info('getParagraphs='+JSON.stringify(this.controller.getParagraphs({ start:1, end:5 }))) 2294 this.content = "" 2295 this.controller.getSpans({ 2296 start: this.start, 2297 end: this.end 2298 }).forEach(item => { 2299 if (typeof (item as RichEditorImageSpanResult)['imageStyle'] != 'undefined') { 2300 if ((item as RichEditorImageSpanResult).valueResourceStr == "") { 2301 console.info("builder span index " + (item as RichEditorImageSpanResult).spanPosition.spanIndex + ", range : " + (item as RichEditorImageSpanResult).offsetInSpan[0] + ", " + 2302 (item as RichEditorImageSpanResult).offsetInSpan[1] + ", size : " + (item as RichEditorImageSpanResult).imageStyle[0] + ", " + (item as RichEditorImageSpanResult).imageStyle[1]) 2303 } else { 2304 console.info("image span " + (item as RichEditorImageSpanResult).valueResourceStr + ", index : " + (item as RichEditorImageSpanResult).spanPosition.spanIndex + ", range: " + 2305 (item as RichEditorImageSpanResult).offsetInSpan[0] + ", " + (item as RichEditorImageSpanResult).offsetInSpan[1] + ", size : " + 2306 (item as RichEditorImageSpanResult).imageStyle.size[0] + ", " + (item as RichEditorImageSpanResult).imageStyle.size[1]) 2307 } 2308 } else { 2309 this.content += (item as RichEditorTextSpanResult).value; 2310 this.content += "\n" 2311 console.info("text span: " + (item as RichEditorTextSpanResult).value) 2312 } 2313 }) 2314 }) 2315 Button("获取选择内容 getSelection").onClick(() => { 2316 this.content = ""; 2317 let select = this.controller.getSelection() 2318 console.info("selection start " + select.selection[0] + " end " + select.selection[1]) 2319 select.spans.forEach(item => { 2320 if (typeof (item as RichEditorImageSpanResult)['imageStyle'] != 'undefined') { 2321 if ((item as RichEditorImageSpanResult).valueResourceStr == "") { 2322 console.info("builder span index " + (item as RichEditorImageSpanResult).spanPosition.spanIndex + ", range : " + (item as RichEditorImageSpanResult).offsetInSpan[0] + ", " + 2323 (item as RichEditorImageSpanResult).offsetInSpan[1] + ", size : " + (item as RichEditorImageSpanResult).imageStyle[0] + ", " + (item as RichEditorImageSpanResult).imageStyle[1]) 2324 } else { 2325 console.info("image span " + (item as RichEditorImageSpanResult).valueResourceStr + ", index : " + (item as RichEditorImageSpanResult).spanPosition.spanIndex + ", range: " + 2326 (item as RichEditorImageSpanResult).offsetInSpan[0] + ", " + (item as RichEditorImageSpanResult).offsetInSpan[1] + ", size : " + 2327 (item as RichEditorImageSpanResult).imageStyle.size[0] + ", " + (item as RichEditorImageSpanResult).imageStyle.size[1]) 2328 } 2329 } else { 2330 this.content += (item as RichEditorTextSpanResult).value; 2331 this.content += "\n" 2332 console.info("text span: " + (item as RichEditorTextSpanResult).value) 2333 } 2334 }) 2335 }) 2336 Button("删除选择内容").onClick(() => { 2337 this.controller.deleteSpans({ 2338 start: this.start, 2339 end: this.end 2340 }) 2341 }) 2342 } 2343 .borderWidth(1) 2344 .borderColor(Color.Red) 2345 .width("100%") 2346 .height("10%") 2347 2348 Column() { 2349 RichEditor(this.option) 2350 .onReady(() => { 2351 this.controller.addTextSpan("0123456789", 2352 { 2353 style: 2354 { 2355 fontColor: Color.Orange, 2356 fontSize: 30 2357 } 2358 }) 2359 this.controller.addImageSpan($r("app.media.icon"), 2360 { 2361 imageStyle: 2362 { 2363 size: ["57px", "57px"] 2364 } 2365 }) 2366 }) 2367 .onSelect((value: RichEditorSelection) => { 2368 this.start = value.selection[0]; 2369 this.end = value.selection[1]; 2370 this.message = "[" + this.start + ", " + this.end + "]" 2371 console.info("onSelect="+JSON.stringify(value)) 2372 }) 2373 .aboutToIMEInput((value: RichEditorInsertValue) => { 2374 console.log("---------------------- aboutToIMEInput --------------------") 2375 console.info("aboutToIMEInput="+JSON.stringify(value)) 2376 console.log("insertOffset:" + value.insertOffset) 2377 console.log("insertValue:" + value.insertValue) 2378 return true; 2379 }) 2380 .onIMEInputComplete((value: RichEditorTextSpanResult) => { 2381 console.log("---------------------- onIMEInputComplete --------------------") 2382 console.info("onIMEInputComplete="+JSON.stringify(value)) 2383 console.log("spanIndex:" + value.spanPosition.spanIndex) 2384 console.log("spanRange:[" + value.spanPosition.spanRange[0] + "," + value.spanPosition.spanRange[1] + "]") 2385 console.log("offsetInSpan:[" + value.offsetInSpan[0] + "," + value.offsetInSpan[1] + "]") 2386 console.log("value:" + value.value) 2387 }) 2388 .aboutToDelete((value: RichEditorDeleteValue) => { 2389 value.richEditorDeleteSpans.forEach(item => { 2390 console.log("---------------------- item --------------------") 2391 console.info("spanIndex=" + item.spanPosition.spanIndex) 2392 console.log("spanRange:[" + item.spanPosition.spanRange[0] + "," + item.spanPosition.spanRange[1] + "]") 2393 console.log("offsetInSpan:[" + item.offsetInSpan[0] + "," + item.offsetInSpan[1] + "]") 2394 if (typeof (item as RichEditorImageSpanResult)['imageStyle'] != 'undefined') { 2395 if ((item as RichEditorImageSpanResult).valueResourceStr == "") { 2396 console.info("builder span index " + (item as RichEditorImageSpanResult).spanPosition.spanIndex + ", range : " + (item as RichEditorImageSpanResult).offsetInSpan[0] + ", " + 2397 (item as RichEditorImageSpanResult).offsetInSpan[1] + ", size : " + (item as RichEditorImageSpanResult).imageStyle[0] + ", " + (item as RichEditorImageSpanResult).imageStyle[1]) 2398 } else { 2399 console.info("image span " + (item as RichEditorImageSpanResult).valueResourceStr + ", index : " + (item as RichEditorImageSpanResult).spanPosition.spanIndex + ", range: " + 2400 (item as RichEditorImageSpanResult).offsetInSpan[0] + ", " + (item as RichEditorImageSpanResult).offsetInSpan[1] + ", size : " + 2401 (item as RichEditorImageSpanResult).imageStyle.size[0] + ", " + (item as RichEditorImageSpanResult).imageStyle.size[1]) 2402 } 2403 } else { 2404 console.info("delete text: " + (item as RichEditorTextSpanResult).value) 2405 } 2406 }) 2407 return true; 2408 }) 2409 .borderWidth(1) 2410 .borderColor(Color.Green) 2411 .width("100%") 2412 .height("30%") 2413 2414 Button("add span") 2415 .onClick(() => { 2416 let num = this.controller.addBuilderSpan(this.my_builder, { offset: this.my_offset }) 2417 console.info('addBuilderSpan return ' + num) 2418 }) 2419 Button("add image") 2420 .onClick(() => { 2421 let num = this.controller.addImageSpan($r("app.media.icon"), { 2422 imageStyle: { 2423 size: ["50px", "50px"], 2424 verticalAlign: ImageSpanAlignment.BOTTOM, 2425 layoutStyle: { 2426 borderRadius: undefined, 2427 margin: undefined 2428 } 2429 } 2430 }) 2431 console.info('addImageSpan return' + num) 2432 }) 2433 Row() { 2434 Button('builder1').onClick(() => { 2435 this.my_builder = () => { 2436 this.placeholderBuilder() 2437 } 2438 }) 2439 Button('builder2').onClick(() => { 2440 this.my_builder = placeholderBuilder2.bind(this) 2441 }) 2442 Button('builder3').onClick(() => { 2443 this.my_builder = () => { 2444 this.placeholderBuilder3() 2445 } 2446 }) 2447 Button('builder4').onClick(() => { 2448 this.my_builder = () => { 2449 this.placeholderBuilder4() 2450 } 2451 }) 2452 } 2453 } 2454 .borderWidth(1) 2455 .borderColor(Color.Red) 2456 .width("100%") 2457 .height("70%") 2458 } 2459 } 2460} 2461``` 2462 2463 2464### 示例10 2465enableDataDetector和dataDetectorConfig使用示例 2466 2467```ts 2468@Entry 2469@Component 2470struct TextExample7 { 2471 controller: RichEditorController = new RichEditorController(); 2472 options: RichEditorOptions = { controller: this.controller }; 2473 @State phoneNumber: string = '(86) (755) ********'; 2474 @State url: string = 'www.********.com'; 2475 @State email: string = '***@example.com'; 2476 @State address: string = 'XX省XX市XX区XXXX'; 2477 @State enableDataDetector: boolean = true; 2478 @State types: TextDataDetectorType[] = []; 2479 2480 build() { 2481 Row() { 2482 Column() { 2483 RichEditor(this.options) 2484 .onReady(() => { 2485 this.controller.addTextSpan('电话号码:' + this.phoneNumber + '\n', 2486 { 2487 style: 2488 { 2489 fontSize: 30 2490 } 2491 }) 2492 this.controller.addTextSpan('链接:' + this.url + '\n', 2493 { 2494 style: 2495 { 2496 fontSize: 30 2497 } 2498 }) 2499 this.controller.addTextSpan('邮箱:' + this.email + '\n', 2500 { 2501 style: 2502 { 2503 fontSize: 30 2504 } 2505 }) 2506 this.controller.addTextSpan('地址:' + this.address, 2507 { 2508 style: 2509 { 2510 fontSize: 30 2511 } 2512 }) 2513 }) 2514 .copyOptions(CopyOptions.InApp) 2515 .enableDataDetector(this.enableDataDetector) 2516 .dataDetectorConfig({types : this.types, onDetectResultUpdate: (result: string)=>{}}) 2517 .borderWidth(1) 2518 .padding(10) 2519 .width('100%') 2520 } 2521 .width('100%') 2522 } 2523 } 2524} 2525``` 2526### 示例11 2527preventDefault使用示例 2528```ts 2529@Entry 2530@Component 2531struct RichEditorDemo { 2532 controller: RichEditorController = new RichEditorController(); 2533 options: RichEditorOptions = { controller: this.controller }; 2534 2535 build() { 2536 Column({ space: 2 }) { 2537 RichEditor(this.options) 2538 .onReady(() => { 2539 this.controller.addTextSpan('RichEditor preventDefault') 2540 }) 2541 .onPaste((event?: PasteEvent) => { 2542 if (event != undefined && event.preventDefault) { 2543 event.preventDefault(); 2544 } 2545 }) 2546 .borderWidth(1) 2547 .borderColor(Color.Green) 2548 .width('100%') 2549 .height('40%') 2550 } 2551 } 2552} 2553``` 2554