当前位置:   article > 正文

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:RichEditor)

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:RichEditor)

支持图文混排和文本交互式编辑的组件。

说明:

该组件从API Version 10开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。 RichEditor仅支持通过onDragStart事件实现浮起等拖拽效果。

子组件

不包含子组件。

接口

RichEditor(value: RichEditorOptions)

参数:

参数名参数类型必填参数描述
valueRichEditorOptions富文本组件初始化选项。

属性

支持通用属性

说明:

其中clip属性默认值为true。 align属性只支持上方丶中间和下方位置的对齐方式。

名称参数类型描述
customKeyboardCustomBuilder设置自定义键盘。
说明:
当设置自定义键盘时,输入框激活后不会打开系统输入法,而是加载指定的自定义组件。
自定义键盘的高度可以通过自定义组件根节点的height属性设置,宽度不可设置,使用系统默认值。
自定义键盘采用覆盖原始界面的方式呈现,不会对应用原始界面产生压缩或者上提。
自定义键盘无法获取焦点,但是会拦截手势事件。
默认在输入控件失去焦点时,关闭自定义键盘。
如果设备支持拍摄输入,设置自定义键盘后,该输入框会不支持拍摄输入。
bindSelectionMenu{
spantype: RichEditorSpanType,
content: CustomBuilder,
responseType: ResponseType | RichEditorResponseType11+,
options?: SelectionMenuOptions
}
设置自定义选择菜单。
默认值:{
spanType: RichEditorSpanType.TEXT
responseType: ResponseType.LongPress
其他:空
}
copyOptionsCopyOptions组件支持设置文本内容是否可复制粘贴。
默认值:CopyOptions.LocalDevice
说明:
copyOptions不为CopyOptions.None时,长按组件内容,会弹出文本选择弹框。如果通过bindSelectionMenu等方式自定义文本选择菜单,则会弹出自定义的菜单。
设置copyOptions为CopyOptions.None,复制、剪切功能不生效。
enableDataDetector11+boolean使能文本识别。
默认值: false
说明:
所识别实体的fontColordecoration会被更改为如下样式:
fontColor:Color.Blue
decoration: {
type: TextDecorationType.Underline,
color: Color.Blue
}
该接口依赖设备底层应具有文本识别能力,否则设置不会生效。
enableDataDetector设置为true,同时不设置dataDetectorConfig属性时,默认识别所有类型的实体。
copyOptions设置为CopyOptions.None时,该功能不会生效。
addBuilderSpan的节点文本,该功能不会生效。
dataDetectorConfig11+TextDataDetectorConfig文本识别配置。
默认值:{
types: [ ],
onDetectResultUpdate: null
}
说明:
需配合enableDataDetector一起使用,设置enableDataDetector为true时,dataDetectorConfig的配置才能生效。

事件

除支持通用事件外,还支持以下事件:

名称功能描述
onReady(callback: () => void)富文本组件初始化完成后,触发回调。
onSelect(callback: (value: RichEditorSelection) => void)鼠标左键按下选择,松开左键后触发回调。
用手指选择时,松开手指触发回调。
- value:选中的所有span信息。
aboutToIMEInput(callback: (value: RichEditorInsertValue) => boolean)输入法输入内容前,触发回调。
- value:输入法将要输入内容信息。
onIMEInputComplete(callback: (value: RichEditorTextSpanResult) => void)输入法完成输入后,触发回调。
- value:输入法完成输入后的文本Span信息。
aboutToDelete(callback: (value: RichEditorDeleteValue) => boolean)输入法删除内容前,触发回调。
- value:准备删除的内容所在的文本Span信息。
onDeleteComplete(callback: () => void)输入法完成删除后,触发回调。
onPaste11+(callback: (event?: PasteEvent) => void)完成粘贴前,触发回调。
说明:
系统的默认粘贴和拖拽行为,只支持纯文本的粘贴。
开发者可以通过该方法,覆盖系统默认行为,实现图文的粘贴。

RichEditorInsertValue

插入文本信息。

名称类型必填说明
insertOffsetnumber插入的文本偏移位置。
insertValuestring插入的文本内容。

RichEditorDeleteValue

名称类型必填说明
offsetnumber删除内容的偏移位置。
directionRichEditorDeleteDirection删除操作的方向。
lengthnumber删除内容长度。
richEditorDeleteSpansArray<RichEditorTextSpanResult | RichEditorImageSpanResult>删除的文本或者图片Span的具体信息。

RichEditorDeleteDirection

删除操作的方向。

名称描述
BACKWARD向后删除。
FORWARD向前删除。

RichEditorTextSpanResult

文本Span信息。

名称类型必填说明
spanPositionRichEditorSpanPositionSpan位置。
valuestring文本Span内容。
textStyleRichEditorTextStyleResult文本Span样式信息。
offsetInSpan[number, number]文本Span内容里有效内容的起始和结束位置。

RichEditorSpanPosition

Span位置信息。

名称类型必填说明
spanIndexnumberSpan索引值。
spanRange[number, number]Span内容在RichEditor内的起始和结束位置。

RichEditorSpanType

Span类型信息。

名称描述
TEXT0Span为文字类型。
IMAGE1Span为图像类型。
MIXED2Span为图文混合类型。

RichEditorTextStyleResult

后端返回的文本样式信息。

名称类型必填描述
fontColorResourceColor文本颜色。
fontSizenumber字体大小。
fontStyleFontStyle字体样式。
fontWeightnumber字体粗细。
fontFamilystring字体列表。
decoration{
type: TextDecorationType,
color: ResourceColor
}
文本装饰线样式及其颜色。

RichEditorImageSpanResult

后端返回的图片信息。

名称类型必填描述
spanPositionRichEditorSpanPositionSpan位置。
valuePixelMapPixelMap图片内容。
valueResourceStrResourceStr图片资源id。
imageStyleRichEditorImageSpanStyleResult图片样式。
offsetInSpan[number, number]Span里图片的起始和结束位置。

RichEditorImageSpanStyleResult

后端返回的图片样式信息。

名称类型必填描述
size[number, number]图片的宽度和高度。
verticalAlignImageSpanAlignment图片垂直对齐方式。
objectFitImageFit图片缩放类型。

RichEditorOptions

RichEditor初始化参数。

名称类型必填说明
controllerRichEditorController富文本控制器。

TextDataDetectorConfig11+

文本识别配置参数。

名称类型必填说明
typesTextDataDetectorType[]文本识别的实体类型。设置typesnull或者[]时,识别所有类型的实体,否则只识别指定类型的实体。
onDetectResultUpdate(result: string) => void文本识别成功后,触发onDetectResultUpdate回调。
result:文本识别的结果,Json格式。

RichEditorController

RichEditor组件的控制器。

导入对象

controller: RichEditorController = new RichEditorController()

getCaretOffset

getCaretOffset(): number

返回当前光标所在位置。

返回值:

类型说明
number当前光标所在位置。

setCaretOffset

setCaretOffset(offset: number): boolean

设置光标位置。

参数:

参数名参数类型必填参数描述
offsetnumber光标偏移位置。超出文本范围时,设置失败。

返回值:

类型说明
boolean光标是否设置成功。

addTextSpan

addTextSpan(value: string, options?: RichEditorTextSpanOptions): number

添加文本内容。

参数:

参数名参数类型必填参数描述
valuestring文本内容。
optionsRichEditorTextSpanOptions文本选项。

返回值:

类型说明
number添加完成的Text Span所在的位置。

addImageSpan

addImageSpan(value: PixelMap | ResourceStr, options?: RichEditorImageSpanOptions): number

添加图片内容。

参数:

参数名参数类型必填参数描述
valuePixelMap|ResourceStr图片内容。
optionsRichEditorImageSpanOptions图片选项。

返回值:

类型说明
number添加完成的imageSpan所在的位置。

addBuilderSpan11+

addBuilderSpan(value: CustomBuilder, options?: RichEditorBuilderSpanOptions): number

说明:

  • RichEditor组件添加占位Span,占位Span调用系统的measure方法计算真实的长宽和位置。
  • 可通过RichEditorBuilderSpanOptions设置此builder在RichEditor中的index(一个文字为一个单位)。
  • 此占位Span不可获焦,不支持拖拽(与可拖拽Span如文本或图片Span一起拖拽时,此Span对应的位置内容不会显示但会占相同大小的区域),支持部分通用属性,占位、删除等能力等同于ImageSpan,长度视为一个文字。
  • 不支持通过bindSelectionMenu设置自定义菜单。
  • 不支持通过getSpansgetSelectiononSelectaboutToDelete获取builderSpan信息。
  • 不支持通过updateSpanStyleupdateParagraphStyle等方式更新builder。
  • 对此builder节点进行复制或粘贴不生效。
  • builder的布局约束由RichEditor传入,如果builder里最外层组件不设置大小,则会用RichEditor的大小作为maxSize。
  • builder的手势相关事件机制与通用手势事件相同,如果builder中未设置透传,则仅有builder中的子组件响应。

通用属性仅支持sizepaddingmarginaspectRatioborderStyleborderWidthborderColorborderRadiusbackgroundColorbackgroundBlurStyleopacityblurbackdropBlurshadowgrayscalebrightnesssaturate、 contrastinvertsepiahueRotatecolorBlendsphericalEffectlightUpEffectpixelStretchEffectlinearGradientBlurclipmaskforegroundBlurStyleaccessibilityGroupaccessibilityTextaccessibilityDescriptionaccessibilityLevel

参数:

参数名参数类型必填参数描述
valueCustomBuilder自定义组件。
optionsRichEditorBuilderSpanOptionsbuilder选项。

返回值:

类型说明
number添加完成的builderSpan所在的位置。

getTypingStyle11+

getTypingStyle(): RichEditorTextStyle

获得用户预设的样式。

返回值:

类型说明
RichEditorTextStyle用户预设样式。

setTypingStyle11+

setTypingStyle(value: RichEditorTextStyle): void

设置用户预设的样式。

参数:

参数名参数类型必填参数描述
valueRichEditorTextStyle预设样式。

updateSpanStyle

updateSpanStyle(value: RichEditorUpdateTextSpanStyleOptions | RichEditorUpdateImageSpanStyleOptions): void

更新文本或者图片样式。
若只更新了一个Span的部分内容,则会根据更新部分、未更新部分将该Span拆分为多个Span。

使用该接口更新文本或图片样式时默认不会关闭自定义文本选择菜单。

参数:

名称类型必填描述
valueRichEditorUpdateTextSpanStyleOptions | RichEditorUpdateImageSpanStyleOptions文本或者图片的样式选项信息。

updateParagraphStyle11+

updateParagraphStyle(value: RichEditorParagraphStyleOptions): void

更新段落的样式。

参数:

名称类型必填描述
valueRichEditorParagraphStyleOptions段落的样式选项信息。

getSpans

getSpans(value?: RichEditorRange): Array<RichEditorTextSpanResult| RichEditorImageSpanResult>

获取span信息。

参数:

参数名参数类型必填参数描述
valueRichEditorRange需要获取span范围。

返回值:

类型说明
Array<RichEditorTextSpanResult | RichEditorImageSpanResult>文本和图片Span信息。

deleteSpans

deleteSpans(value?: RichEditorRange): void

删除指定范围内的文本和图片。

参数:

参数名参数类型必填参数描述
valueRichEditorRange删除范围。省略时,删除所有文本和图片。

getParagraphs11+

getParagraphs(value?: RichEditorRange): Array<RichEditorParagraphResult>

获得指定返回的段落。

参数:

参数名参数类型必填参数描述
valueRichEditorRange需要获取段落的范围。

返回值:

类型说明
Array<RichEditorParagraphResult>选中段落的信息。

closeSelectionMenu

closeSelectionMenu(): void

关闭自定义选择菜单或系统默认选择菜单。

setSelection11+

setSelection(selectionStart: number, selectionEnd: number)

支持设置文本选中,选中部分背板高亮。

selectionStart和selectionEnd均为-1时表示全选。

接口调用前有带手柄菜单弹出时则调用后不主动关闭菜单,且调整菜单位置。

接口调用前有不带手柄菜单弹出时则调用后不主动关闭菜单,且保持菜单原来位置。

接口调用前无菜单弹出,则调用后也无菜单弹出。

未获焦时调用该接口不产生选中效果。

使用示例

参数:

参数名参数类型必填参数描述
selectionStartnumber选中开始位置。
selectionEndnumber选中结束位置。

getSelection11+

getSelection(): RichEditorSelection

获取选中文本内容。

使用示例

返回值:

类型说明
RichEditorSelection选中内容信息。

RichEditorSelection

选中内容信息。

名称类型必填说明
selection[number, number]选中范围。
spansArray<RichEditorTextSpanResultRichEditorImageSpanResult>span信息。

RichEditorUpdateTextSpanStyleOptions

文本样式选项。

名称类型必填描述
startnumber需要更新样式的文本起始位置,省略或者设置负值时表示从0开始。
endnumber需要更新样式的文本结束位置,省略或者超出文本范围时表示无穷大。
textStyleRichEditorTextStyle文本样式。

RichEditorUpdateImageSpanStyleOptions

图片样式选项。

名称类型必填描述
startnumber需要更新样式的图片起始位置,省略或者设置负值时表示从0开始。
endnumber需要更新样式的图片结束位置,省略或者超出文本范围时表示无穷大。
imageStyleRichEditorImageSpanStyle图片样式。

RichEditorParagraphStyleOptions11+

段落样式选项

名称类型必填描述
startnumber需要更新样式的段落起始位置,省略或者设置负值时表示从0开始。
endnumber需要更新样式的段落结束位置,省略、负数或者超出文本范围时表示无穷大。
styleRichEditorParagraphStyle段落样式。

RichEditorParagraphStyle11+

段落样式。

名称类型必填描述
textAlignTextAlign设置文本段落在水平方向的对齐方式。
leadingMarginDimension | LeadingMarginPlaceholder设置文本段落缩进,不支持设置百分比。

LeadingMarginPlaceholder11+

前导边距跨度。

名称类型必填描述
pixelMapPixelMap图片内容。
size[DimensionDimension]图片大小,不支持设置百分比。

RichEditorParagraphResult11+

后端返回的段落信息。

名称类型必填描述
styleRichEditorParagraphStyle段落样式。
range[number, number]段落起始位置。

RichEditorTextSpanOptions

添加文本的偏移位置和文本样式信息。

名称类型必填描述
offsetnumber添加文本的位置。省略时,添加到所有文本字符串的最后。
当值小于0时,放在字符串最前面;当值大于字符串长度时,放在字符串最后面。
styleRichEditorTextStyle文本样式信息。省略时,使用系统默认文本信息。
paragraphStyle11+RichEditorParagraphStyle段落样式。
gesture11+RichEditorGesture行为触发回调。省略时,仅使用系统默认行为。

RichEditorTextStyle

文本样式信息。

名称类型必填描述
fontColorResourceColor文本颜色。
默认值:Color.Black。
fontSizeLength | number设置字体大小,Length为number类型时,使用fp单位。字体默认大小16。不支持设置百分比字符串。
从API version 9开始,该接口支持在ArkTS卡片中使用。
fontStyleFontStyle字体样式。
默认值:FontStyle.Normal。
fontWeightFontWeight | number | string字体粗细。
number类型取值[100,900],取值间隔为100,默认为400,取值越大,字体越粗。
string类型仅支持number类型取值的字符串形式,例如“400”,以及“bold”、“bolder”、“lighter”、“regular” 、“medium”分别对应FontWeight中相应的枚举值。
默认值:FontWeight.Normal。
fontFamilyResourceStr设置字体列表。默认字体'HarmonyOS Sans',当前支持'HarmonyOS Sans'字体和注册自定义字体
默认字体:'HarmonyOS Sans'。
decoration{
type: TextDecorationType,
color?: ResourceColor
}
设置文本装饰线样式及其颜色。
默认值:{
type: TextDecorationType.None,
color:Color.Black
}。
textShadow11+ShadowOptions | Array<ShadowOptions>设置文字阴影效果。该接口支持以数组形式入参,实现多重文字阴影。
说明:
不支持fill字段, 不支持智能取色模式。

RichEditorImageSpanOptions

添加图片的偏移位置和图片样式信息。

名称类型必填描述
offsetnumber添加图片的位置。省略时,添加到所有文本字符串的最后。
当值小于0时,放在字符串最前面;当值大于字符串长度时,放在字符串最后面。
imageStyleRichEditorImageSpanStyle图片样式信息。省略时,使用系统默认图片信息。
gesture11+RichEditorGesture行为触发回调。省略时,仅使用系统默认行为。

RichEditorImageSpanStyle

图片样式。

名称类型必填描述
size[DimensionDimension]图片宽度和高度。
verticalAlignImageSpanAlignment图片垂直对齐方式。
默认值:ImageSpanAlignment.BASELINE
objectFitImageFit图片缩放类型。
默认值:ImageFit.Cover。
layoutStyle11+{
margin ?: Dimension | Margin,
borderRadius ?: Dimension | BorderRadiuses
}
图片布局风格。

RichEditorBuilderSpanOptions11+

添加图片的偏移位置和图片样式信息。

名称类型必填描述
offsetnumber添加builder的位置。省略或者为异常值时,添加到所有文本字符串的最后。

RichEditorRange

范围信息。

名称类型必填描述
startnumber起始位置,省略或者设置负值时表示从0开始。
endnumber结束位置,省略或者超出文本范围时表示无穷大。

SelectionMenuOptions11+

范围信息。

名称类型必填描述
onAppear() => void自定义选择菜单弹出时回调。
onDisappear() => void自定义选择菜单关闭时回调。

PasteEvent11+

定义用户粘贴事件。

名称类型必填描述
preventDefault() => void用户自定义粘贴事件。
存在时会覆盖系统粘贴事件。

RichEditorGesture11+

用户行为回调。

onClick11+

onClick(callback: (event?: ClickEvent) => void)

点击完成时回调事件。
双击时,第一次点击触发回调事件。

参数:

参数名参数类型必填描述
eventClickEvent用户点击事件。

onLongPress11+

onLongPress(callback: (event?: GestureEvent) => void )

长按完成时回调事件。

参数:

参数名参数类型必填描述
eventGestureEvent用户长按事件。

示例

示例1

  1. // xxx.ets
  2. @Entry
  3. @Component
  4. struct Index {
  5. controller: RichEditorController = new RichEditorController();
  6. options: RichEditorOptions = { controller: this.controller };
  7. private start: number = -1;
  8. private end: number = -1;
  9. @State message: string = "[-1, -1]"
  10. @State content: string = ""
  11. build() {
  12. Column() {
  13. Column() {
  14. Text("selection range:").width("100%")
  15. Text() {
  16. Span(this.message)
  17. }.width("100%")
  18. Text("selection content:").width("100%")
  19. Text() {
  20. Span(this.content)
  21. }.width("100%")
  22. }
  23. .borderWidth(1)
  24. .borderColor(Color.Red)
  25. .width("100%")
  26. .height("20%")
  27. Row() {
  28. Button("更新样式:加粗").onClick(() => {
  29. this.controller.updateSpanStyle({
  30. start: this.start,
  31. end: this.end,
  32. textStyle:
  33. {
  34. fontWeight: FontWeight.Bolder
  35. }
  36. })
  37. })
  38. Button("获取选择内容").onClick(() => {
  39. this.content = "";
  40. this.controller.getSpans({
  41. start: this.start,
  42. end: this.end
  43. }).forEach(item => {
  44. if(typeof(item as RichEditorImageSpanResult)['imageStyle'] != 'undefined'){
  45. this.content += (item as RichEditorImageSpanResult).valueResourceStr;
  46. this.content += "\n"
  47. } else {
  48. this.content += (item as RichEditorTextSpanResult).value;
  49. this.content += "\n"
  50. }
  51. })
  52. })
  53. Button("删除选择内容").onClick(() => {
  54. this.controller.deleteSpans({
  55. start: this.start,
  56. end: this.end
  57. })
  58. this.start = -1;
  59. this.end = -1;
  60. this.message = "[" + this.start + ", " + this.end + "]"
  61. })
  62. }
  63. .borderWidth(1)
  64. .borderColor(Color.Red)
  65. .width("100%")
  66. .height("10%")
  67. Column() {
  68. RichEditor(this.options)
  69. .onReady(() => {
  70. this.controller.addTextSpan("0123456789",
  71. {
  72. style:
  73. {
  74. fontColor: Color.Orange,
  75. fontSize: 30
  76. }
  77. })
  78. this.controller.addImageSpan($r("app.media.icon"),
  79. {
  80. imageStyle:
  81. {
  82. size: ["57px", "57px"]
  83. }
  84. })
  85. this.controller.addTextSpan("0123456789",
  86. {
  87. style:
  88. {
  89. fontColor: Color.Black,
  90. fontSize: 30
  91. }
  92. })
  93. })
  94. .onSelect((value: RichEditorSelection) => {
  95. this.start = value.selection[0];
  96. this.end = value.selection[1];
  97. this.message = "[" + this.start + ", " + this.end + "]"
  98. })
  99. .aboutToIMEInput((value: RichEditorInsertValue) => {
  100. console.log("---------------------- aboutToIMEInput ----------------------")
  101. console.log("insertOffset:" + value.insertOffset)
  102. console.log("insertValue:" + value.insertValue)
  103. return true;
  104. })
  105. .onIMEInputComplete((value: RichEditorTextSpanResult) => {
  106. console.log("---------------------- onIMEInputComplete ---------------------")
  107. console.log("spanIndex:" + value.spanPosition.spanIndex)
  108. console.log("spanRange:[" + value.spanPosition.spanRange[0] + "," + value.spanPosition.spanRange[1] + "]")
  109. console.log("offsetInSpan:[" + value.offsetInSpan[0] + "," + value.offsetInSpan[1] + "]")
  110. console.log("value:" + value.value)
  111. })
  112. .aboutToDelete((value: RichEditorDeleteValue) => {
  113. console.log("---------------------- aboutToDelete --------------------------")
  114. console.log("offset:" + value.offset)
  115. console.log("direction:" + value.direction)
  116. console.log("length:" + value.length)
  117. value.richEditorDeleteSpans.forEach(item => {
  118. console.log("---------------------- item --------------------------")
  119. console.log("spanIndex:" + item.spanPosition.spanIndex)
  120. console.log("spanRange:[" + item.spanPosition.spanRange[0] + "," + item.spanPosition.spanRange[1] + "]")
  121. console.log("offsetInSpan:[" + item.offsetInSpan[0] + "," + item.offsetInSpan[1] + "]")
  122. if (typeof(item as RichEditorImageSpanResult)['imageStyle'] != 'undefined') {
  123. console.log("image:" + (item as RichEditorImageSpanResult).valueResourceStr)
  124. } else {
  125. console.log("text:" + (item as RichEditorTextSpanResult).value)
  126. }
  127. })
  128. return true;
  129. })
  130. .onDeleteComplete(() => {
  131. console.log("---------------------- onDeleteComplete ------------------------")
  132. })
  133. .borderWidth(1)
  134. .borderColor(Color.Green)
  135. .width("100%")
  136. .height("30%")
  137. }
  138. .borderWidth(1)
  139. .borderColor(Color.Red)
  140. .width("100%")
  141. .height("70%")
  142. }
  143. }
  144. }

richeditor

示例2

  1. // xxx.ets
  2. @Entry
  3. @Component
  4. struct RichEditorExample {
  5. controller: RichEditorController = new RichEditorController()
  6. // 自定义键盘组件
  7. @Builder CustomKeyboardBuilder() {
  8. Column() {
  9. Grid() {
  10. ForEach([1, 2, 3, 4, 5, 6, 7, 8, 9, '*', 0, '#'], (item: number | string) => {
  11. GridItem() {
  12. Button(item + "")
  13. .width(110).onClick(() => {
  14. this.controller.addTextSpan(item + '', {
  15. offset: this.controller.getCaretOffset(),
  16. style:
  17. {
  18. fontColor: Color.Orange,
  19. fontSize: 30
  20. }
  21. })
  22. this.controller.setCaretOffset(this.controller.getCaretOffset() + item.toString().length)
  23. })
  24. }
  25. })
  26. }.maxCount(3).columnsGap(10).rowsGap(10).padding(5)
  27. }.backgroundColor(Color.Gray)
  28. }
  29. build() {
  30. Column() {
  31. RichEditor({ controller: this.controller })
  32. // 绑定自定义键盘
  33. .customKeyboard(this.CustomKeyboardBuilder()).margin(10).border({ width: 1 })
  34. .height(200)
  35. .borderWidth(1)
  36. .borderColor(Color.Red)
  37. .width("100%")
  38. }
  39. }
  40. }

customKeyboard

示例3

  1. // xxx.ets
  2. import pasteboard from '@ohos.pasteboard'
  3. import { BusinessError } from '@ohos.base';
  4. export interface SelectionMenuTheme {
  5. imageSize: number;
  6. buttonSize: number;
  7. menuSpacing: number;
  8. editorOptionMargin: number;
  9. expandedOptionPadding: number;
  10. defaultMenuWidth: number;
  11. imageFillColor: Resource;
  12. backGroundColor: Resource;
  13. iconBorderRadius: Resource;
  14. containerBorderRadius: Resource;
  15. cutIcon: Resource;
  16. copyIcon: Resource;
  17. pasteIcon: Resource;
  18. selectAllIcon: Resource;
  19. shareIcon: Resource;
  20. translateIcon: Resource;
  21. searchIcon: Resource;
  22. arrowDownIcon: Resource;
  23. iconPanelShadowStyle: ShadowStyle;
  24. iconFocusBorderColor: Resource;
  25. }
  26. export const defaultTheme: SelectionMenuTheme = {
  27. imageSize: 24,
  28. buttonSize: 48,
  29. menuSpacing: 8,
  30. editorOptionMargin: 1,
  31. expandedOptionPadding: 3,
  32. defaultMenuWidth: 256,
  33. imageFillColor: $r('sys.color.ohos_id_color_primary'),
  34. backGroundColor: $r('sys.color.ohos_id_color_dialog_bg'),
  35. iconBorderRadius: $r('sys.float.ohos_id_corner_radius_default_m'),
  36. containerBorderRadius: $r('sys.float.ohos_id_corner_radius_card'),
  37. cutIcon: $r("sys.media.ohos_ic_public_cut"),
  38. copyIcon: $r("sys.media.ohos_ic_public_copy"),
  39. pasteIcon: $r("sys.media.ohos_ic_public_paste"),
  40. selectAllIcon: $r("sys.media.ohos_ic_public_select_all"),
  41. shareIcon: $r("sys.media.ohos_ic_public_share"),
  42. translateIcon: $r("sys.media.ohos_ic_public_translate_c2e"),
  43. searchIcon: $r("sys.media.ohos_ic_public_search_filled"),
  44. arrowDownIcon: $r("sys.media.ohos_ic_public_arrow_down"),
  45. iconPanelShadowStyle: ShadowStyle.OUTER_DEFAULT_MD,
  46. iconFocusBorderColor: $r('sys.color.ohos_id_color_focused_outline'),
  47. }
  48. @Entry
  49. @Component
  50. struct SelectionMenu {
  51. @State message: string = 'Hello World'
  52. @State textSize: number = 40
  53. @State sliderShow: boolean = false
  54. @State start: number = -1
  55. @State end: number = -1
  56. @State colorTransparent: Color = Color.Transparent
  57. controller: RichEditorController = new RichEditorController();
  58. options: RichEditorOptions = { controller: this.controller }
  59. private iconArr: Array<Resource> =
  60. [$r('app.media.icon'), $r("app.media.icon"), $r('app.media.icon'),
  61. $r("app.media.icon"), $r('app.media.icon')]
  62. @State iconBgColor: ResourceColor[] = new Array(this.iconArr.length).fill(this.colorTransparent)
  63. @State pasteEnable: boolean = false
  64. @State visibilityValue: Visibility = Visibility.Visible
  65. @State textStyle: RichEditorTextStyle = {}
  66. private fontWeightTable: string[] = ["100", "200", "300", "400", "500", "600", "700", "800", "900", "bold", "normal", "bolder", "lighter", "medium", "regular"]
  67. private theme: SelectionMenuTheme = defaultTheme;
  68. aboutToAppear() {
  69. if (this.controller) {
  70. let richEditorSelection = this.controller.getSelection()
  71. if (richEditorSelection) {
  72. let start = richEditorSelection.selection[0]
  73. let end = richEditorSelection.selection[1]
  74. if (start === 0 && this.controller.getSpans({ start: end + 1, end: end + 1 }).length === 0) {
  75. this.visibilityValue = Visibility.None
  76. } else {
  77. this.visibilityValue = Visibility.Visible
  78. }
  79. }
  80. }
  81. let sysBoard = pasteboard.getSystemPasteboard()
  82. if (sysBoard && sysBoard.hasDataSync()) {
  83. this.pasteEnable = true
  84. } else {
  85. this.pasteEnable = false
  86. }
  87. }
  88. build() {
  89. Column() {
  90. Column() {
  91. RichEditor(this.options)
  92. .onReady(() => {
  93. this.controller.addTextSpan(this.message, { style: { fontColor: Color.Orange, fontSize: 30 } })
  94. })
  95. .onSelect((value: RichEditorSelection) => {
  96. if (value.selection[0] == -1 && value.selection[1] == -1) {
  97. return
  98. }
  99. this.start = value.selection[0]
  100. this.end = value.selection[1]
  101. })
  102. .bindSelectionMenu(RichEditorSpanType.TEXT, this.panel, ResponseType.LongPress, { onDisappear: () => {
  103. this.sliderShow = false
  104. }})
  105. .bindSelectionMenu(RichEditorSpanType.TEXT, this.panel, ResponseType.RightClick, { onDisappear: () => {
  106. this.sliderShow = false
  107. }})
  108. .borderWidth(1)
  109. .borderColor(Color.Red)
  110. .width(200)
  111. .height(200)
  112. }.width('100%').backgroundColor(Color.White)
  113. }.height('100%')
  114. }
  115. PushDataToPasteboard(richEditorSelection: RichEditorSelection) {
  116. let sysBoard = pasteboard.getSystemPasteboard()
  117. let pasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, '')
  118. if (richEditorSelection.spans && richEditorSelection.spans.length > 0) {
  119. let count = richEditorSelection.spans.length
  120. for (let i = count - 1; i >= 0; i--) {
  121. let item = richEditorSelection.spans[i]
  122. if ((item as RichEditorTextSpanResult)?.textStyle) {
  123. let span = item as RichEditorTextSpanResult
  124. let style = span.textStyle
  125. let data = pasteboard.createRecord(pasteboard.MIMETYPE_TEXT_PLAIN, span.value.substring(span.offsetInSpan[0], span.offsetInSpan[1]))
  126. let prop = pasteData.getProperty()
  127. let temp: Record<string, Object> = {
  128. 'color': style.fontColor,
  129. 'size': style.fontSize,
  130. 'style': style.fontStyle,
  131. 'weight': this.fontWeightTable[style.fontWeight],
  132. 'fontFamily': style.fontFamily,
  133. 'decorationType': style.decoration.type,
  134. 'decorationColor': style.decoration.color
  135. }
  136. prop.additions[i] = temp;
  137. pasteData.addRecord(data)
  138. pasteData.setProperty(prop)
  139. }
  140. }
  141. }
  142. sysBoard.clearData()
  143. sysBoard.setData(pasteData).then(() => {
  144. console.info('SelectionMenu copy option, Succeeded in setting PasteData.');
  145. this.pasteEnable = true;
  146. }).catch((err: BusinessError) => {
  147. console.error('SelectionMenu copy option, Failed to set PasteData. Cause:' + err.message);
  148. })
  149. }
  150. PopDataFromPasteboard(richEditorSelection: RichEditorSelection) {
  151. let start = richEditorSelection.selection[0]
  152. let end = richEditorSelection.selection[1]
  153. if (start == end && this.controller) {
  154. start = this.controller.getCaretOffset()
  155. end = this.controller.getCaretOffset()
  156. }
  157. let moveOffset = 0
  158. let sysBoard = pasteboard.getSystemPasteboard()
  159. sysBoard.getData((err, data) => {
  160. if (err) {
  161. return
  162. }
  163. let count = data.getRecordCount()
  164. for (let i = 0; i < count; i++) {
  165. const element = data.getRecord(i);
  166. let tex: RichEditorTextStyle = {
  167. fontSize: 16,
  168. fontColor: Color.Black,
  169. fontWeight: FontWeight.Normal,
  170. fontFamily: "HarmonyOS Sans",
  171. fontStyle: FontStyle.Normal,
  172. decoration: { type: TextDecorationType.None, color: "#FF000000" }
  173. }
  174. if (data.getProperty() && data.getProperty().additions[i]) {
  175. const tmp = data.getProperty().additions[i] as Record<string, Object | undefined>;
  176. if (tmp.color) {
  177. tex.fontColor = tmp.color as ResourceColor;
  178. }
  179. if (tmp.size) {
  180. tex.fontSize = tmp.size as Length | number;
  181. }
  182. if (tmp.style) {
  183. tex.fontStyle = tmp.style as FontStyle;
  184. }
  185. if (tmp.weight) {
  186. tex.fontWeight = tmp.weight as number | FontWeight | string;
  187. }
  188. if (tmp.fontFamily) {
  189. tex.fontFamily = tmp.fontFamily as ResourceStr;
  190. }
  191. if (tmp.decorationType && tex.decoration) {
  192. tex.decoration.type = tmp.decorationType as TextDecorationType;
  193. }
  194. if (tmp.decorationColor && tex.decoration) {
  195. tex.decoration.color = tmp.decorationColor as ResourceColor;
  196. }
  197. if (tex.decoration) {
  198. tex.decoration = { type: tex.decoration.type, color: tex.decoration.color }
  199. }
  200. }
  201. if (element && element.plainText && element.mimeType === pasteboard.MIMETYPE_TEXT_PLAIN && this.controller) {
  202. this.controller.addTextSpan(element.plainText,
  203. {
  204. style: tex,
  205. offset: start + moveOffset
  206. }
  207. )
  208. moveOffset += element.plainText.length
  209. }
  210. }
  211. if (this.controller) {
  212. this.controller.setCaretOffset(start + moveOffset)
  213. this.controller.closeSelectionMenu()
  214. }
  215. if (start != end && this.controller) {
  216. this.controller.deleteSpans({ start: start + moveOffset, end: end + moveOffset })
  217. }
  218. })
  219. }
  220. @Builder
  221. panel() {
  222. Column() {
  223. this.iconPanel()
  224. if (!this.sliderShow) {
  225. this.SystemMenu()
  226. } else {
  227. this.sliderPanel()
  228. }
  229. }.width(256)
  230. }
  231. @Builder iconPanel() {
  232. Column() {
  233. Row({ space: 2 }) {
  234. ForEach(this.iconArr, (item:Resource, index ?: number) => {
  235. Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
  236. Image(item).fillColor(this.theme.imageFillColor).width(24).height(24).focusable(true).draggable(false)
  237. }
  238. .borderRadius(this.theme.iconBorderRadius)
  239. .width(this.theme.buttonSize)
  240. .height(this.theme.buttonSize)
  241. .onClick(() => {
  242. if (index as number == 0) {
  243. this.sliderShow = false
  244. if (this.controller) {
  245. let selection = this.controller.getSelection();
  246. let spans = selection.spans
  247. spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
  248. if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
  249. let span = item as RichEditorTextSpanResult
  250. this.textStyle = span.textStyle
  251. let start = span.offsetInSpan[0]
  252. let end = span.offsetInSpan[1]
  253. let offset = span.spanPosition.spanRange[0]
  254. if (this.textStyle.fontWeight != 11) {
  255. this.textStyle.fontWeight = FontWeight.Bolder
  256. } else {
  257. this.textStyle.fontWeight = FontWeight.Normal
  258. }
  259. this.controller.updateSpanStyle({
  260. start: offset + start,
  261. end: offset + end,
  262. textStyle: this.textStyle
  263. })
  264. }
  265. })
  266. }
  267. } else if (index as number == 1) {
  268. this.sliderShow = false
  269. if (this.controller) {
  270. let selection = this.controller.getSelection();
  271. let spans = selection.spans
  272. spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
  273. if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
  274. let span = item as RichEditorTextSpanResult
  275. this.textStyle = span.textStyle
  276. let start = span.offsetInSpan[0]
  277. let end = span.offsetInSpan[1]
  278. let offset = span.spanPosition.spanRange[0]
  279. if (this.textStyle.fontStyle == FontStyle.Italic) {
  280. this.textStyle.fontStyle = FontStyle.Normal
  281. } else {
  282. this.textStyle.fontStyle = FontStyle.Italic
  283. }
  284. this.controller.updateSpanStyle({
  285. start: offset + start,
  286. end: offset + end,
  287. textStyle: this.textStyle
  288. })
  289. }
  290. })
  291. }
  292. } else if (index as number == 2) {
  293. this.sliderShow = false
  294. if (this.controller) {
  295. let selection = this.controller.getSelection();
  296. let spans = selection.spans
  297. spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
  298. if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
  299. let span = item as RichEditorTextSpanResult
  300. this.textStyle = span.textStyle
  301. let start = span.offsetInSpan[0]
  302. let end = span.offsetInSpan[1]
  303. let offset = span.spanPosition.spanRange[0]
  304. if (this.textStyle.decoration) {
  305. if (this.textStyle.decoration.type == TextDecorationType.Underline) {
  306. this.textStyle.decoration.type = TextDecorationType.None
  307. } else {
  308. this.textStyle.decoration.type = TextDecorationType.Underline
  309. }
  310. } else {
  311. this.textStyle.decoration = { type: TextDecorationType.Underline, color: Color.Black }
  312. }
  313. this.controller.updateSpanStyle({
  314. start: offset + start,
  315. end: offset + end,
  316. textStyle: this.textStyle
  317. })
  318. }
  319. })
  320. }
  321. } else if (index as number == 3) {
  322. this.sliderShow = !this.sliderShow
  323. } else if (index as number == 4) {
  324. this.sliderShow = false
  325. if (this.controller) {
  326. let selection = this.controller.getSelection();
  327. let spans = selection.spans
  328. spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
  329. if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
  330. let span = item as RichEditorTextSpanResult
  331. this.textStyle = span.textStyle
  332. let start = span.offsetInSpan[0]
  333. let end = span.offsetInSpan[1]
  334. let offset = span.spanPosition.spanRange[0]
  335. if (this.textStyle.fontColor == Color.Orange || this.textStyle.fontColor == '#FFFFA500') {
  336. this.textStyle.fontColor = Color.Black
  337. } else {
  338. this.textStyle.fontColor = Color.Orange
  339. }
  340. this.controller.updateSpanStyle({
  341. start: offset + start,
  342. end: offset + end,
  343. textStyle: this.textStyle
  344. })
  345. }
  346. })
  347. }
  348. }
  349. })
  350. .onTouch((event?: TouchEvent | undefined) => {
  351. if(event != undefined){
  352. if (event.type === TouchType.Down) {
  353. this.iconBgColor[index as number] = $r('sys.color.ohos_id_color_click_effect')
  354. }
  355. if (event.type === TouchType.Up) {
  356. this.iconBgColor[index as number] = this.colorTransparent
  357. }
  358. }
  359. })
  360. .onHover((isHover?: boolean, event?: HoverEvent) => {
  361. this.iconBgColor.forEach((icon:ResourceColor, index1) => {
  362. this.iconBgColor[index1] = this.colorTransparent
  363. })
  364. if(isHover != undefined) {
  365. this.iconBgColor[index as number] = $r('sys.color.ohos_id_color_hover')
  366. }
  367. })
  368. .backgroundColor(this.iconBgColor[index as number])
  369. })
  370. }
  371. }
  372. .clip(true)
  373. .width(this.theme.defaultMenuWidth)
  374. .padding(this.theme.expandedOptionPadding)
  375. .borderRadius(this.theme.containerBorderRadius)
  376. .margin({ bottom: this.theme.menuSpacing })
  377. .backgroundColor(this.theme.backGroundColor)
  378. .shadow(this.theme.iconPanelShadowStyle)
  379. }
  380. @Builder
  381. SystemMenu() {
  382. Column() {
  383. Menu() {
  384. if (this.controller) {
  385. MenuItemGroup() {
  386. MenuItem({ startIcon: this.theme.cutIcon, content: "剪切", labelInfo: "Ctrl+X" })
  387. .onClick(() => {
  388. if (!this.controller) {
  389. return
  390. }
  391. let richEditorSelection = this.controller.getSelection()
  392. this.PushDataToPasteboard(richEditorSelection);
  393. this.controller.deleteSpans({
  394. start: richEditorSelection.selection[0],
  395. end: richEditorSelection.selection[1]
  396. })
  397. })
  398. MenuItem({ startIcon: this.theme.copyIcon, content: "复制", labelInfo: "Ctrl+C" })
  399. .onClick(() => {
  400. if (!this.controller) {
  401. return
  402. }
  403. let richEditorSelection = this.controller.getSelection()
  404. this.PushDataToPasteboard(richEditorSelection);
  405. this.controller.closeSelectionMenu()
  406. })
  407. MenuItem({ startIcon: this.theme.pasteIcon, content: "粘贴", labelInfo: "Ctrl+V" })
  408. .enabled(this.pasteEnable)
  409. .onClick(() => {
  410. if (!this.controller) {
  411. return
  412. }
  413. let richEditorSelection = this.controller.getSelection()
  414. this.PopDataFromPasteboard(richEditorSelection)
  415. })
  416. MenuItem({ startIcon: this.theme.selectAllIcon, content: "全选", labelInfo: "Ctrl+A" })
  417. .visibility(this.visibilityValue)
  418. .onClick(() => {
  419. if (!this.controller) {
  420. return
  421. }
  422. this.controller.setSelection(-1, -1)
  423. this.visibilityValue = Visibility.None
  424. })
  425. MenuItem({ startIcon: this.theme.shareIcon, content: "分享", labelInfo: "" })
  426. .enabled(false)
  427. MenuItem({ startIcon: this.theme.translateIcon, content: "翻译", labelInfo: "" })
  428. .enabled(false)
  429. MenuItem({ startIcon: this.theme.searchIcon, content: "搜索", labelInfo: "" })
  430. .enabled(false)
  431. }
  432. }
  433. }
  434. .onVisibleAreaChange([0.0, 1.0], () => {
  435. if (!this.controller) {
  436. return
  437. }
  438. let richEditorSelection = this.controller.getSelection()
  439. let start = richEditorSelection.selection[0]
  440. let end = richEditorSelection.selection[1]
  441. if (start === 0 && this.controller.getSpans({ start: end + 1, end: end + 1 }).length === 0) {
  442. this.visibilityValue = Visibility.None
  443. } else {
  444. this.visibilityValue = Visibility.Visible
  445. }
  446. })
  447. .radius(this.theme.containerBorderRadius)
  448. .clip(true)
  449. .backgroundColor(Color.White)
  450. .width(this.theme.defaultMenuWidth)
  451. }
  452. .width(this.theme.defaultMenuWidth)
  453. }
  454. @Builder sliderPanel() {
  455. Column() {
  456. Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
  457. Text('A').fontSize(15)
  458. Slider({ value: this.textSize, step: 10, style: SliderStyle.InSet })
  459. .width(210)
  460. .onChange((value: number, mode: SliderChangeMode) => {
  461. if (this.controller) {
  462. let selection = this.controller.getSelection();
  463. if (mode == SliderChangeMode.End) {
  464. if (this.textSize == undefined) {
  465. this.textSize = 0
  466. }
  467. let spans = selection.spans
  468. spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
  469. if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
  470. this.textSize = Math.max(this.textSize, (item as RichEditorTextSpanResult).textStyle.fontSize)
  471. }
  472. })
  473. }
  474. if (mode == SliderChangeMode.Moving || mode == SliderChangeMode.Click) {
  475. this.start = selection.selection[0]
  476. this.end = selection.selection[1]
  477. this.textSize = value
  478. this.controller.updateSpanStyle({
  479. start: this.start,
  480. end: this.end,
  481. textStyle: { fontSize: this.textSize }
  482. })
  483. }
  484. }
  485. })
  486. Text('A').fontSize(20).fontWeight(FontWeight.Medium)
  487. }.borderRadius(this.theme.containerBorderRadius)
  488. }
  489. .shadow(ShadowStyle.OUTER_DEFAULT_MD)
  490. .backgroundColor(Color.White)
  491. .borderRadius(this.theme.containerBorderRadius)
  492. .padding(15)
  493. .height(48)
  494. }
  495. }

说明:

系统暂未预置加粗、斜体等图标,示例代码使用系统默认图标,开发者使用时需自行替换iconArr中的资源。

selectionMenu

示例4

  1. // xxx.ets
  2. @Entry
  3. @Component
  4. struct Index {
  5. controller: RichEditorController = new RichEditorController();
  6. options: RichEditorOptions = { controller: this.controller };
  7. private start: number = -1;
  8. private end: number = -1;
  9. @State message: string = "[-1, -1]"
  10. @State content: string = ""
  11. @State paddingVal: number = 5
  12. @State borderRad: number = 4
  13. build() {
  14. Column() {
  15. Column() {
  16. Text("selection range:").width("100%")
  17. Text() {
  18. Span(this.message)
  19. }.width("100%")
  20. Text("selection content:").width("100%")
  21. Text() {
  22. Span(this.content)
  23. }.width("100%")
  24. }
  25. .borderWidth(1)
  26. .borderColor(Color.Red)
  27. .width("100%")
  28. .height("20%")
  29. Row() {
  30. Button("updateSpanStyle1")
  31. .fontSize(12)
  32. .onClick(() => {
  33. this.controller.updateSpanStyle({
  34. start: this.start,
  35. textStyle:
  36. {
  37. fontWeight: FontWeight.Bolder
  38. },
  39. imageStyle: {
  40. size: ["80px", "80px"],
  41. layoutStyle: {
  42. borderRadius: undefined,
  43. margin: undefined
  44. }
  45. }
  46. })
  47. })
  48. Button("updateSpanStyle2")
  49. .fontSize(12)
  50. .onClick(() => {
  51. this.controller.updateSpanStyle({
  52. start: this.start,
  53. textStyle:
  54. {
  55. fontWeight: FontWeight.Bolder
  56. },
  57. imageStyle: {
  58. size: ["70px", "70px"],
  59. layoutStyle: {
  60. borderRadius: { topLeft: '100px', topRight: '20px', bottomLeft: '100px', bottomRight: '20px' },
  61. margin: { left: '30px', top: '20px', right: '20px', bottom: '20px' }
  62. }
  63. }
  64. })
  65. })
  66. Button("updateSpanStyle3")
  67. .fontSize(12)
  68. .onClick(() => {
  69. this.controller.updateSpanStyle({
  70. start: this.start,
  71. textStyle:
  72. {
  73. fontWeight: FontWeight.Bolder
  74. },
  75. imageStyle: {
  76. size: ["60px", "60px"],
  77. layoutStyle: {
  78. borderRadius: '-10px',
  79. margin: '-10px'
  80. }
  81. }
  82. })
  83. })
  84. }
  85. .borderWidth(1)
  86. .borderColor(Color.Red)
  87. .width("100%")
  88. .height("10%")
  89. Row() {
  90. Button('addImageSpan1')
  91. .fontSize(12)
  92. .onClick(() => {
  93. this.controller.addImageSpan($r('app.media.app_icon'), {
  94. imageStyle: {
  95. size: ["80px", "80px"],
  96. layoutStyle: {
  97. borderRadius: '50px',
  98. margin: '40px'
  99. }
  100. }
  101. })
  102. })
  103. Button('addImageSpan2')
  104. .fontSize(12)
  105. .onClick(() => {
  106. this.controller.addImageSpan($r('app.media.app_icon'), {
  107. imageStyle: {
  108. size: ["100px", "100px"],
  109. verticalAlign: ImageSpanAlignment.BOTTOM,
  110. layoutStyle: {
  111. borderRadius: undefined,
  112. margin: undefined
  113. }
  114. }
  115. })
  116. })
  117. Button('addImageSpan3')
  118. .fontSize(12)
  119. .onClick(() => {
  120. this.controller.addImageSpan($r('app.media.app_icon'), {
  121. imageStyle: {
  122. size: ["60px", "60px"],
  123. verticalAlign: ImageSpanAlignment.BOTTOM,
  124. layoutStyle: {
  125. borderRadius: { topLeft: '10px', topRight: '20px', bottomLeft: '30px', bottomRight: '40px' },
  126. margin: { left: '10px', top: '20px', right: '30px', bottom: '40px' }
  127. }
  128. }
  129. })
  130. })
  131. }
  132. .borderWidth(1)
  133. .borderColor(Color.Red)
  134. .width("100%")
  135. .height("10%")
  136. Column() {
  137. RichEditor(this.options)
  138. .onReady(() => {
  139. this.controller.addTextSpan("0123456789",
  140. {
  141. style:
  142. {
  143. fontColor: Color.Orange,
  144. fontSize: 30
  145. }
  146. })
  147. this.controller.addImageSpan($r("app.media.app_icon"),
  148. {
  149. imageStyle:
  150. {
  151. size: ["60px", "60px"],
  152. verticalAlign: ImageSpanAlignment.BOTTOM,
  153. layoutStyle: {
  154. borderRadius: { topLeft: '10px', topRight: '20px', bottomLeft: '30px', bottomRight: '40px' },
  155. margin: { left: '10px', top: '20px', right: '30px', bottom: '40px' }
  156. }
  157. }
  158. })
  159. this.controller.addTextSpan("0123456789",
  160. {
  161. style:
  162. {
  163. fontColor: Color.Black,
  164. fontSize: 30
  165. }
  166. })
  167. })
  168. .onSelect((value: RichEditorSelection) => {
  169. this.start = value.selection[0];
  170. this.end = value.selection[1];
  171. this.message = "[" + this.start + ", " + this.end + "]"
  172. })
  173. .aboutToIMEInput((value: RichEditorInsertValue) => {
  174. console.log("---------------------- aboutToIMEInput ----------------------")
  175. console.log("insertOffset:" + value.insertOffset)
  176. console.log("insertValue:" + value.insertValue)
  177. return true;
  178. })
  179. .onIMEInputComplete((value: RichEditorTextSpanResult) => {
  180. console.log("---------------------- onIMEInputComplete ---------------------")
  181. console.log("spanIndex:" + value.spanPosition.spanIndex)
  182. console.log("spanRange:[" + value.spanPosition.spanRange[0] + "," + value.spanPosition.spanRange[1] + "]")
  183. console.log("offsetInSpan:[" + value.offsetInSpan[0] + "," + value.offsetInSpan[1] + "]")
  184. console.log("value:" + value.value)
  185. })
  186. .aboutToDelete((value: RichEditorDeleteValue) => {
  187. console.log("---------------------- aboutToDelete --------------------------")
  188. console.log("offset:" + value.offset)
  189. console.log("direction:" + value.direction)
  190. console.log("length:" + value.length)
  191. value.richEditorDeleteSpans.forEach(item => {
  192. console.log("---------------------- item --------------------------")
  193. console.log("spanIndex:" + item.spanPosition.spanIndex)
  194. console.log("spanRange:[" + item.spanPosition.spanRange[0] + "," + item.spanPosition.spanRange[1] + "]")
  195. console.log("offsetInSpan:[" + item.offsetInSpan[0] + "," + item.offsetInSpan[1] + "]")
  196. if (typeof (item as RichEditorImageSpanResult)['imageStyle'] != 'undefined') {
  197. console.log("image:" + (item as RichEditorImageSpanResult).valueResourceStr)
  198. } else {
  199. console.log("text:" + (item as RichEditorTextSpanResult).value)
  200. }
  201. })
  202. return true;
  203. })
  204. .onDeleteComplete(() => {
  205. console.log("---------------------- onDeleteComplete ------------------------")
  206. })
  207. .borderWidth(1)
  208. .borderColor(Color.Green)
  209. .width("100%")
  210. .height('80.00%')
  211. }
  212. .borderWidth(1)
  213. .borderColor(Color.Red)
  214. .width("100%")
  215. .height("70%")
  216. }
  217. }
  218. }

ImageSpanStyle

示例5

  1. // xxx.ets
  2. @Entry
  3. @Component
  4. struct Index {
  5. controller: RichEditorController = new RichEditorController()
  6. options: RichEditorOptions = { controller: this.controller };
  7. @State textFlag: string = "TextFlag";
  8. build() {
  9. Column() {
  10. Column() {
  11. Text(this.textFlag)
  12. .copyOption(CopyOptions.InApp)
  13. .fontSize(50)
  14. }
  15. Divider()
  16. Column() {
  17. RichEditor(this.options)
  18. .onReady(() => {
  19. this.controller.addTextSpan('Area1\n', {
  20. style:
  21. {
  22. fontColor: Color.Orange,
  23. fontSize: 50
  24. },
  25. gesture:
  26. {
  27. onClick: () => {
  28. this.textFlag = "Area1 is onClick."
  29. },
  30. onLongPress: () => {
  31. this.textFlag = "Area1 is onLongPress."
  32. }
  33. }
  34. })
  35. this.controller.addTextSpan('Area2\n', {
  36. style:
  37. {
  38. fontColor: Color.Blue,
  39. fontSize: 50
  40. },
  41. gesture:
  42. {
  43. onClick: () => {
  44. this.textFlag = "Area2 is onClick."
  45. },
  46. onLongPress: () => {
  47. this.textFlag = "Area2 is onLongPress."
  48. }
  49. }
  50. })
  51. this.controller.addImageSpan($r("app.media.icon"),
  52. {
  53. imageStyle:
  54. {
  55. size: ["100px", "100px"],
  56. layoutStyle: {
  57. margin: 5,
  58. borderRadius: 15
  59. }
  60. },
  61. gesture:
  62. {
  63. onClick: () => {
  64. this.textFlag = "ImageSpan is onClick."
  65. },
  66. onLongPress: () => {
  67. this.textFlag = "ImageSpan is onLongPress."
  68. }
  69. }
  70. })
  71. })
  72. }
  73. .borderWidth(1)
  74. .borderColor(Color.Red)
  75. .width("100%")
  76. .height("70%")
  77. }
  78. }
  79. }

OnClickAndLongPress

示例6

  1. // xxx.ets
  2. @Entry
  3. @Component
  4. struct Index {
  5. controller: RichEditorController = new RichEditorController();
  6. private spanParagraphs: RichEditorParagraphResult[] = [];
  7. build() {
  8. Column() {
  9. RichEditor({ controller: this.controller })
  10. .onReady(() => {
  11. this.controller.addTextSpan("0123456789\n", {
  12. style: {
  13. fontColor: Color.Pink,
  14. fontSize: "32",
  15. },
  16. paragraphStyle: {
  17. textAlign: TextAlign.Start,
  18. leadingMargin: 16
  19. }
  20. })
  21. this.controller.addTextSpan("0123456789")
  22. })
  23. .width("80%")
  24. .height("30%")
  25. .border({ width: 1, radius: 5 })
  26. .draggable(false)
  27. Column({ space: 5 }) {
  28. Button("段落左对齐").onClick(() => {
  29. this.controller.updateParagraphStyle({ start: -1, end: -1,
  30. style: {
  31. textAlign: TextAlign.Start,
  32. }
  33. })
  34. })
  35. Button("段落右对齐").onClick(() => {
  36. this.controller.updateParagraphStyle({ start: -1, end: -1,
  37. style: {
  38. textAlign: TextAlign.End,
  39. }
  40. })
  41. })
  42. Button("段落居中").onClick(() => {
  43. this.controller.updateParagraphStyle({ start: -1, end: -1,
  44. style: {
  45. textAlign: TextAlign.Center,
  46. }
  47. })
  48. })
  49. Divider()
  50. Button("getParagraphs").onClick(() => {
  51. this.spanParagraphs = this.controller.getParagraphs({ start: -1, end: -1 })
  52. console.log("RichEditor getParagraphs:" + JSON.stringify(this.spanParagraphs))
  53. })
  54. Button("UpdateSpanStyle1").onClick(() => {
  55. this.controller.updateSpanStyle({ start: -1, end: -1,
  56. textStyle: {
  57. fontColor: Color.Brown,
  58. fontSize: 20
  59. }
  60. })
  61. })
  62. Button("UpdateSpanStyle2").onClick(() => {
  63. this.controller.updateSpanStyle({ start: -1, end: -1,
  64. textStyle: {
  65. fontColor: Color.Green,
  66. fontSize: 30
  67. }
  68. })
  69. })
  70. }
  71. }
  72. }
  73. }

TextAlignAndGetParagraphInfo

示例7

  1. // xxx.ets
  2. import font from '@ohos.font'
  3. const canvasWidth = 1000
  4. const canvasHeight = 100
  5. const Indentation = 40
  6. class LeadingMarginCreator {
  7. private settings: RenderingContextSettings = new RenderingContextSettings(true)
  8. private offscreenCanvas: OffscreenCanvas = new OffscreenCanvas(canvasWidth, canvasHeight)
  9. private offContext: OffscreenCanvasRenderingContext2D = this.offscreenCanvas.getContext("2d", this.settings)
  10. public static instance: LeadingMarginCreator = new LeadingMarginCreator()
  11. // 获得字体字号级别,分别是从04
  12. public getFontSizeLevel(fontSize: number) {
  13. const fontScaled: number = Number(fontSize) / 16
  14. enum FontSizeScaleThreshold {
  15. SMALL = 0.9,
  16. NORMAL = 1.1,
  17. LEVEL_1_LARGE = 1.2,
  18. LEVEL_2_LARGE = 1.4,
  19. LEVEL_3_LARGE = 1.5
  20. }
  21. let fontSizeLevel: number = 1
  22. if (fontScaled < FontSizeScaleThreshold.SMALL) {
  23. fontSizeLevel = 0
  24. } else if (fontScaled < FontSizeScaleThreshold.NORMAL) {
  25. fontSizeLevel = 1
  26. } else if (fontScaled < FontSizeScaleThreshold.LEVEL_1_LARGE) {
  27. fontSizeLevel = 2
  28. } else if (fontScaled < FontSizeScaleThreshold.LEVEL_2_LARGE) {
  29. fontSizeLevel = 3
  30. } else if (fontScaled < FontSizeScaleThreshold.LEVEL_3_LARGE) {
  31. fontSizeLevel = 4
  32. } else {
  33. fontSizeLevel = 1
  34. }
  35. return fontSizeLevel
  36. }
  37. // 获得字体字号级别,分别是从04
  38. public getmarginLevel(Width: number) {
  39. let marginlevel: number = 1
  40. if (Width == 40) {
  41. marginlevel = 2.0
  42. } else if (Width == 80) {
  43. marginlevel = 1.0
  44. } else if (Width == 120) {
  45. marginlevel = 2/3
  46. } else if (Width == 160) {
  47. marginlevel = 0.5
  48. } else if (Width == 200) {
  49. marginlevel = 0.4
  50. }
  51. return marginlevel
  52. }
  53. public genStrMark(fontSize: number, str: string): PixelMap {
  54. this.offContext = this.offscreenCanvas.getContext("2d", this.settings)
  55. this.clearCanvas()
  56. this.offContext.font = fontSize + 'vp sans-serif'
  57. this.offContext.fillText(str + '.', 0, fontSize * 0.9)
  58. return this.offContext.getPixelMap(0, 0, fontSize * (str.length + 1) / 1.75, fontSize)
  59. }
  60. public genSquareMark(fontSize: number): PixelMap {
  61. this.offContext = this.offscreenCanvas.getContext("2d", this.settings)
  62. this.clearCanvas()
  63. const coordinate = fontSize * (1 - 1 / 1.5) / 2
  64. const sideLength = fontSize / 1.5
  65. this.offContext.fillRect(coordinate, coordinate, sideLength, sideLength)
  66. return this.offContext.getPixelMap(0, 0, fontSize, fontSize)
  67. }
  68. // 生成圆圈符号
  69. public genCircleMark(fontSize: number, width: number, level?: number ): PixelMap {
  70. const indentLevel = level ?? 1
  71. const offsetLevel = [22, 28, 32, 34, 38]
  72. const fontSizeLevel = this.getFontSizeLevel(fontSize)
  73. const marginlevel = this.getmarginLevel(width)
  74. const newOffContext: OffscreenCanvasRenderingContext2D = this.offscreenCanvas.getContext("2d", this.settings)
  75. const centerCoordinate = 50
  76. const radius = 10
  77. this.clearCanvas()
  78. newOffContext.ellipse(100 * (indentLevel + 1) - centerCoordinate * marginlevel, offsetLevel[fontSizeLevel], radius * marginlevel, radius, 0, 0, 2 * Math.PI)
  79. newOffContext.fillStyle = '66FF0000'
  80. newOffContext.fill()
  81. return newOffContext.getPixelMap(0, 0, 100 + 100 * indentLevel, 100)
  82. }
  83. private clearCanvas() {
  84. this.offContext.clearRect(0, 0, canvasWidth, canvasHeight)
  85. }
  86. }
  87. @Entry
  88. @Component
  89. struct Index {
  90. controller: RichEditorController = new RichEditorController()
  91. options: RichEditorOptions = { controller: this.controller }
  92. private leadingMarkCreatorInstance = LeadingMarginCreator.instance
  93. private fontNameRawFile: string = 'MiSans-Bold'
  94. @State fs: number = 30
  95. @State cl: number = Color.Black
  96. private leftMargin: Dimension = 0
  97. private richEditorTextStyle: RichEditorTextStyle = {}
  98. aboutToAppear() {
  99. font.registerFont({
  100. familyName: 'MiSans-Bold',
  101. familySrc: '/font/MiSans-Bold.ttf'
  102. })
  103. }
  104. build() {
  105. Scroll() {
  106. Column() {
  107. RichEditor(this.options)
  108. .onReady(() => {
  109. this.controller.addTextSpan("0123456789\n",
  110. {
  111. style:
  112. {
  113. fontWeight: 'medium',
  114. fontFamily: this.fontNameRawFile,
  115. fontColor: Color.Red,
  116. fontSize: 50,
  117. fontStyle: FontStyle.Italic,
  118. decoration: { type: TextDecorationType.Underline, color: Color.Green }
  119. }
  120. })
  121. this.controller.addTextSpan("abcdefg",
  122. {
  123. style:
  124. {
  125. fontWeight: FontWeight.Lighter,
  126. fontFamily: 'HarmonyOS Sans',
  127. fontColor: 'rgba(0,128,0,0.5)',
  128. fontSize: 30,
  129. fontStyle: FontStyle.Normal,
  130. decoration: { type: TextDecorationType.Overline, color: 'rgba(169, 26, 246, 0.50)' }
  131. }
  132. })
  133. })
  134. .borderWidth(1)
  135. .borderColor(Color.Green)
  136. .width("100%")
  137. .height("50%")
  138. Row({ space: 5 }) {
  139. Button('setTypingStyle1')
  140. .fontSize(10)
  141. .onClick(() => {
  142. this.controller.setTypingStyle(
  143. {
  144. fontWeight: 'medium',
  145. fontFamily: this.fontNameRawFile,
  146. fontColor: Color.Blue,
  147. fontSize: 50,
  148. fontStyle: FontStyle.Italic,
  149. decoration: { type: TextDecorationType.Underline, color: Color.Green }
  150. })
  151. })
  152. Button('setTypingStyle2')
  153. .fontSize(10)
  154. .onClick(() => {
  155. this.controller.setTypingStyle(
  156. {
  157. fontWeight: FontWeight.Lighter,
  158. fontFamily: 'HarmonyOS Sans',
  159. fontColor: Color.Green,
  160. fontSize: '30',
  161. fontStyle: FontStyle.Normal,
  162. decoration: { type: TextDecorationType.Overline, color: 'rgba(169, 26, 246, 0.50)' }
  163. })
  164. })
  165. }
  166. Divider()
  167. Button("getTypingStyle").onClick(() => {
  168. this.richEditorTextStyle = this.controller.getTypingStyle()
  169. console.log("RichEditor getTypingStyle:" + JSON.stringify(this.richEditorTextStyle))
  170. })
  171. Divider()
  172. Row({ space: 5 }) {
  173. Button("向右列表缩进").onClick(() => {
  174. let margin = Number(this.leftMargin)
  175. if (margin < 200) {
  176. margin += Indentation
  177. this.leftMargin = margin
  178. }
  179. this.controller.updateParagraphStyle({
  180. start: -10,
  181. end: -10,
  182. style: {
  183. leadingMargin : {
  184. pixelMap : this.leadingMarkCreatorInstance.genCircleMark(100, margin, 1),
  185. size: [margin, 40]
  186. }
  187. }
  188. })
  189. })
  190. Button("向左列表缩进").onClick(() => {
  191. let margin = Number(this.leftMargin)
  192. if (margin > 0) {
  193. margin -= Indentation
  194. this.leftMargin = margin
  195. }
  196. this.controller.updateParagraphStyle({
  197. start: -10,
  198. end: -10,
  199. style: {
  200. leadingMargin : {
  201. pixelMap : this.leadingMarkCreatorInstance.genCircleMark(100, margin, 1),
  202. size: [margin, 40]
  203. }
  204. }
  205. })
  206. })
  207. }
  208. Divider()
  209. Row({ space: 5 }) {
  210. Button("向右空白缩进").onClick(() => {
  211. let margin = Number(this.leftMargin)
  212. if (margin < 200) {
  213. margin += Indentation
  214. this.leftMargin = margin
  215. }
  216. this.controller.updateParagraphStyle({
  217. start: -10,
  218. end: -10,
  219. style: {
  220. leadingMargin: margin
  221. }
  222. })
  223. })
  224. Button("向左空白缩进").onClick(() => {
  225. let margin = Number(this.leftMargin)
  226. if (margin > 0) {
  227. margin -= Indentation
  228. this.leftMargin = margin
  229. }
  230. this.controller.updateParagraphStyle({
  231. start: -10,
  232. end: -10,
  233. style: {
  234. leadingMargin: margin
  235. }
  236. })
  237. })
  238. }
  239. }.borderWidth(1).borderColor(Color.Red)
  240. }
  241. }
  242. }

UpdateParagraphAndTypingStyle

示例8

  1. @Entry
  2. @Component
  3. struct Index {
  4. controller: RichEditorController = new RichEditorController();
  5. options: RichEditorOptions = { controller: this.controller };
  6. private start: number = -1;
  7. private end: number = -1;
  8. @State message: string = "[-1, -1]"
  9. @State content: string = ""
  10. @State visable :number = 0;
  11. @State index:number = 0;
  12. @State offsetx: number = 0;
  13. @State textShadows : (ShadowOptions | Array<ShadowOptions> ) =
  14. [{ radius: 10, color: Color.Red, offsetX: 10, offsetY: 0 },{ radius: 10, color: Color.Black, offsetX: 20, offsetY: 0 },
  15. { radius: 10, color: Color.Brown, offsetX: 30, offsetY: 0 },{ radius: 10, color: Color.Green, offsetX: 40, offsetY: 0 },
  16. { radius: 10, color: Color.Yellow, offsetX: 100, offsetY: 0 }]
  17. @State textshadowOf : ShadowOptions[] = []
  18. build() {
  19. Column() {
  20. Column() {
  21. Text("selection range:").width("100%")
  22. Text() {
  23. Span(this.message)
  24. }.width("100%")
  25. Text("selection content:").width("100%")
  26. Text() {
  27. Span(this.content)
  28. }.width("100%")
  29. }
  30. .borderWidth(1)
  31. .borderColor(Color.Red)
  32. .width("100%")
  33. .height("20%")
  34. Row() {
  35. Button("更新样式: 加粗 & 文本阴影").onClick(() => {
  36. this.controller.updateSpanStyle({
  37. start: this.start,
  38. end: this.end,
  39. textStyle:
  40. {
  41. fontWeight: FontWeight.Bolder,
  42. textShadow: this.textShadows
  43. }
  44. })
  45. })
  46. }
  47. .borderWidth(1)
  48. .borderColor(Color.Red)
  49. .width("100%")
  50. .height("10%")
  51. Column() {
  52. RichEditor(this.options)
  53. .onReady(() => {
  54. this.controller.addTextSpan("0123456789",
  55. {
  56. style:
  57. {
  58. fontColor: Color.Orange,
  59. fontSize: 30,
  60. textShadow: { radius: 10, color: Color.Blue, offsetX: 10, offsetY: 0 }
  61. }
  62. })
  63. })
  64. .borderWidth(1)
  65. .borderColor(Color.Green)
  66. .width("100%")
  67. .height("30%")
  68. }
  69. .borderWidth(1)
  70. .borderColor(Color.Red)
  71. .width("100%")
  72. .height("70%")
  73. }
  74. }
  75. }

TextshadowExample

示例9

  1. @Builder
  2. function placeholderBuilder2() {
  3. Row({ space: 2 }) {
  4. Image($r("app.media.icon")).width(24).height(24).margin({ left: -5 })
  5. Text('okokokok').fontSize(10)
  6. }.width('20%').height(50).padding(10).backgroundColor(Color.Red)
  7. }
  8. // xxx.ets
  9. @Entry
  10. @Component
  11. struct Index {
  12. controller: RichEditorController = new RichEditorController();
  13. option: RichEditorOptions = { controller: this.controller };
  14. private start: number = 2;
  15. private end: number = 4;
  16. @State message: string = "[-1, -1]"
  17. @State content: string = ""
  18. private my_offset: number | undefined = undefined
  19. private my_builder: CustomBuilder = undefined
  20. @Builder
  21. placeholderBuilder() {
  22. Row({ space: 2 }) {
  23. Image($r("app.media.icon")).width(24).height(24).margin({ left: -5 })
  24. Text('Custom Popup').fontSize(10)
  25. }.width(100).height(50).padding(5)
  26. }
  27. @Builder
  28. placeholderBuilder3() {
  29. Text("hello").padding('20').borderWidth(1).width('100%')
  30. }
  31. @Builder
  32. placeholderBuilder4() {
  33. Column() {
  34. Column({ space: 5 }) {
  35. Text('direction:Row').fontSize(9).fontColor(0xCCCCCC).width('90%')
  36. Flex({ direction: FlexDirection.Row }) { // 子组件在容器主抽上行布局
  37. Text('1').width('20%').height(50).backgroundColor(0xF5DEB3)
  38. Text('1').width('20%').height(50).backgroundColor(0xD2B48C)
  39. Text('1').width('20%').height(50).backgroundColor(0xF5DEB3)
  40. Text('1').width('20%').height(50).backgroundColor(0xD2B48C)
  41. }
  42. .height(70)
  43. .width('90%')
  44. .padding(10)
  45. .backgroundColor(0xAFEEEE)
  46. Text('direction:RowReverse').fontSize(9).fontColor(0xCCCCCC).width('90%')
  47. Flex({ direction: FlexDirection.RowReverse }) { // 子组件在容器主抽上反向行布局
  48. Text('1').width('20%').height(50).backgroundColor(0xF5DEB3)
  49. Text('1').width('20%').height(50).backgroundColor(0xD2B48C)
  50. Text('1').width('20%').height(50).backgroundColor(0xF5DEB3)
  51. Text('1').width('20%').height(50).backgroundColor(0xD2B48C)
  52. }
  53. .height(70)
  54. .width('90%')
  55. .padding(10)
  56. .backgroundColor(0xAFEEEE)
  57. Text('direction:Column').fontSize(9).fontColor(0xCCCCCC).width('90%')
  58. Flex({ direction: FlexDirection.Column }) { // 子组件在容器主抽上列布局
  59. Text('1').width('20%').height(40).backgroundColor(0xF5DEB3)
  60. Text('1').width('20%').height(40).backgroundColor(0xD2B48C)
  61. Text('1').width('20%').height(40).backgroundColor(0xF5DEB3)
  62. Text('1').width('20%').height(40).backgroundColor(0xD2B48C)
  63. }
  64. .height(160)
  65. .width('90%')
  66. .padding(10)
  67. .backgroundColor(0xAFEEEE)
  68. Text('direction:ColumnReverse').fontSize(9).fontColor(0xCCCCCC).width('90%')
  69. Flex({ direction: FlexDirection.ColumnReverse }) { // 子组件在容器主抽上反向列布局
  70. Text('1').width('20%').height(40).backgroundColor(0xF5DEB3)
  71. Text('1').width('20%').height(40).backgroundColor(0xD2B48C)
  72. Text('1').width('20%').height(40).backgroundColor(0xF5DEB3)
  73. Text('1').width('20%').height(40).backgroundColor(0xD2B48C)
  74. }
  75. .height(160)
  76. .width('90%')
  77. .padding(10)
  78. .backgroundColor(0xAFEEEE)
  79. }.width('100%').margin({ top: 5 })
  80. }.width('100%')
  81. }
  82. @Builder
  83. MyMenu() {
  84. Menu() {
  85. MenuItem({ startIcon: $r("app.media.icon"), content: "菜单选项1" })
  86. MenuItem({ startIcon: $r("app.media.icon"), content: "菜单选项2" })
  87. .enabled(false)
  88. }
  89. }
  90. build() {
  91. Column() {
  92. Column() {
  93. Text("selection range:").width("100%")
  94. Text() {
  95. Span(this.message)
  96. }.width("100%")
  97. Text("selection content:").width("100%")
  98. Text() {
  99. Span(this.content)
  100. }.width("100%")
  101. }
  102. .borderWidth(1)
  103. .borderColor(Color.Red)
  104. .width("100%")
  105. .height("20%")
  106. Row() {
  107. Button("获取选择内容 getSpans").onClick(() => {
  108. console.info('getSpans='+JSON.stringify(this.controller.getSpans({ start:1, end:5 })))
  109. console.info('getParagraphs='+JSON.stringify(this.controller.getParagraphs({ start:1, end:5 })))
  110. this.content = ""
  111. this.controller.getSpans({
  112. start: this.start,
  113. end: this.end
  114. }).forEach(item => {
  115. if (typeof (item as RichEditorImageSpanResult)['imageStyle'] != 'undefined') {
  116. if ((item as RichEditorImageSpanResult).valueResourceStr == "") {
  117. console.info("builder span index " + (item as RichEditorImageSpanResult).spanPosition.spanIndex + ", range : " + (item as RichEditorImageSpanResult).offsetInSpan[0] + ", " +
  118. (item as RichEditorImageSpanResult).offsetInSpan[1] + ", size : " + (item as RichEditorImageSpanResult).imageStyle[0] + ", " + (item as RichEditorImageSpanResult).imageStyle[1])
  119. } else {
  120. console.info("image span " + (item as RichEditorImageSpanResult).valueResourceStr + ", index : " + (item as RichEditorImageSpanResult).spanPosition.spanIndex + ", range: " +
  121. (item as RichEditorImageSpanResult).offsetInSpan[0] + ", " + (item as RichEditorImageSpanResult).offsetInSpan[1] + ", size : " +
  122. (item as RichEditorImageSpanResult).imageStyle.size[0] + ", " + (item as RichEditorImageSpanResult).imageStyle.size[1])
  123. }
  124. } else {
  125. this.content += (item as RichEditorTextSpanResult).value;
  126. this.content += "\n"
  127. console.info("text span: " + (item as RichEditorTextSpanResult).value)
  128. }
  129. })
  130. })
  131. Button("获取选择内容 getSelection").onClick(() => {
  132. this.content = "";
  133. let select = this.controller.getSelection()
  134. console.info("selection start " + select.selection[0] + " end " + select.selection[1])
  135. select.spans.forEach(item => {
  136. if (typeof (item as RichEditorImageSpanResult)['imageStyle'] != 'undefined') {
  137. if ((item as RichEditorImageSpanResult).valueResourceStr == "") {
  138. console.info("builder span index " + (item as RichEditorImageSpanResult).spanPosition.spanIndex + ", range : " + (item as RichEditorImageSpanResult).offsetInSpan[0] + ", " +
  139. (item as RichEditorImageSpanResult).offsetInSpan[1] + ", size : " + (item as RichEditorImageSpanResult).imageStyle[0] + ", " + (item as RichEditorImageSpanResult).imageStyle[1])
  140. } else {
  141. console.info("image span " + (item as RichEditorImageSpanResult).valueResourceStr + ", index : " + (item as RichEditorImageSpanResult).spanPosition.spanIndex + ", range: " +
  142. (item as RichEditorImageSpanResult).offsetInSpan[0] + ", " + (item as RichEditorImageSpanResult).offsetInSpan[1] + ", size : " +
  143. (item as RichEditorImageSpanResult).imageStyle.size[0] + ", " + (item as RichEditorImageSpanResult).imageStyle.size[1])
  144. }
  145. } else {
  146. this.content += (item as RichEditorTextSpanResult).value;
  147. this.content += "\n"
  148. console.info("text span: " + (item as RichEditorTextSpanResult).value)
  149. }
  150. })
  151. })
  152. Button("删除选择内容").onClick(() => {
  153. this.controller.deleteSpans({
  154. start: this.start,
  155. end: this.end
  156. })
  157. })
  158. }
  159. .borderWidth(1)
  160. .borderColor(Color.Red)
  161. .width("100%")
  162. .height("10%")
  163. Column() {
  164. RichEditor(this.option)
  165. .onReady(() => {
  166. this.controller.addTextSpan("0123456789",
  167. {
  168. style:
  169. {
  170. fontColor: Color.Orange,
  171. fontSize: 30
  172. }
  173. })
  174. this.controller.addImageSpan($r("app.media.icon"),
  175. {
  176. imageStyle:
  177. {
  178. size: ["57px", "57px"]
  179. }
  180. })
  181. })
  182. .onSelect((value: RichEditorSelection) => {
  183. this.start = value.selection[0];
  184. this.end = value.selection[1];
  185. this.message = "[" + this.start + ", " + this.end + "]"
  186. console.info("onSelect="+JSON.stringify(value))
  187. })
  188. .aboutToIMEInput((value: RichEditorInsertValue) => {
  189. console.log("---------------------- aboutToIMEInput --------------------")
  190. console.info("aboutToIMEInput="+JSON.stringify(value))
  191. console.log("insertOffset:" + value.insertOffset)
  192. console.log("insertValue:" + value.insertValue)
  193. return true;
  194. })
  195. .onIMEInputComplete((value: RichEditorTextSpanResult) => {
  196. console.log("---------------------- onIMEInputComplete --------------------")
  197. console.info("onIMEInputComplete="+JSON.stringify(value))
  198. console.log("spanIndex:" + value.spanPosition.spanIndex)
  199. console.log("spanRange:[" + value.spanPosition.spanRange[0] + "," + value.spanPosition.spanRange[1] + "]")
  200. console.log("offsetInSpan:[" + value.offsetInSpan[0] + "," + value.offsetInSpan[1] + "]")
  201. console.log("value:" + value.value)
  202. })
  203. .aboutToDelete((value: RichEditorDeleteValue) => {
  204. value.richEditorDeleteSpans.forEach(item => {
  205. console.log("---------------------- item --------------------")
  206. console.info("spanIndex=" + item.spanPosition.spanIndex)
  207. console.log("spanRange:[" + item.spanPosition.spanRange[0] + "," + item.spanPosition.spanRange[1] + "]")
  208. console.log("offsetInSpan:[" + item.offsetInSpan[0] + "," + item.offsetInSpan[1] + "]")
  209. if (typeof (item as RichEditorImageSpanResult)['imageStyle'] != 'undefined') {
  210. if ((item as RichEditorImageSpanResult).valueResourceStr == "") {
  211. console.info("builder span index " + (item as RichEditorImageSpanResult).spanPosition.spanIndex + ", range : " + (item as RichEditorImageSpanResult).offsetInSpan[0] + ", " +
  212. (item as RichEditorImageSpanResult).offsetInSpan[1] + ", size : " + (item as RichEditorImageSpanResult).imageStyle[0] + ", " + (item as RichEditorImageSpanResult).imageStyle[1])
  213. } else {
  214. console.info("image span " + (item as RichEditorImageSpanResult).valueResourceStr + ", index : " + (item as RichEditorImageSpanResult).spanPosition.spanIndex + ", range: " +
  215. (item as RichEditorImageSpanResult).offsetInSpan[0] + ", " + (item as RichEditorImageSpanResult).offsetInSpan[1] + ", size : " +
  216. (item as RichEditorImageSpanResult).imageStyle.size[0] + ", " + (item as RichEditorImageSpanResult).imageStyle.size[1])
  217. }
  218. } else {
  219. console.info("delete text: " + (item as RichEditorTextSpanResult).value)
  220. }
  221. })
  222. return true;
  223. })
  224. .borderWidth(1)
  225. .borderColor(Color.Green)
  226. .width("100%")
  227. .height("30%")
  228. Button("add span")
  229. .onClick(() => {
  230. let num = this.controller.addBuilderSpan(this.my_builder, { offset: this.my_offset })
  231. console.info('addBuilderSpan return ' + num)
  232. })
  233. Button("add image")
  234. .onClick(() => {
  235. let num = this.controller.addImageSpan($r("app.media.icon"), {
  236. imageStyle: {
  237. size: ["50px", "50px"],
  238. verticalAlign: ImageSpanAlignment.BOTTOM,
  239. layoutStyle: {
  240. borderRadius: undefined,
  241. margin: undefined
  242. }
  243. }
  244. })
  245. console.info('addImageSpan return' + num)
  246. })
  247. Row() {
  248. Button('builder1').onClick(() => {
  249. this.my_builder = () => {
  250. this.placeholderBuilder()
  251. }
  252. })
  253. Button('builder2').onClick(() => {
  254. this.my_builder = placeholderBuilder2.bind(this)
  255. })
  256. Button('builder3').onClick(() => {
  257. this.my_builder = () => {
  258. this.placeholderBuilder3()
  259. }
  260. })
  261. Button('builder4').onClick(() => {
  262. this.my_builder = () => {
  263. this.placeholderBuilder4()
  264. }
  265. })
  266. }
  267. }
  268. .borderWidth(1)
  269. .borderColor(Color.Red)
  270. .width("100%")
  271. .height("70%")
  272. }
  273. }
  274. }

AddBuilderSpanExample

示例10

enableDataDetector和dataDetectorConfig使用示例

  1. @Entry
  2. @Component
  3. struct TextExample7 {
  4. controller: RichEditorController = new RichEditorController();
  5. options: RichEditorOptions = { controller: this.controller };
  6. @State phoneNumber: string = '(86) (755) ********';
  7. @State url: string = 'www.********.com';
  8. @State email: string = '***@example.com';
  9. @State address: string = 'XX省XX市XX区XXXX';
  10. @State enableDataDetector: boolean = true;
  11. @State types: TextDataDetectorType[] = [];
  12. build() {
  13. Row() {
  14. Column() {
  15. RichEditor(this.options)
  16. .onReady(() => {
  17. this.controller.addTextSpan('电话号码:' + this.phoneNumber + '\n',
  18. {
  19. style:
  20. {
  21. fontSize: 30
  22. }
  23. })
  24. this.controller.addTextSpan('链接:' + this.url + '\n',
  25. {
  26. style:
  27. {
  28. fontSize: 30
  29. }
  30. })
  31. this.controller.addTextSpan('邮箱:' + this.email + '\n',
  32. {
  33. style:
  34. {
  35. fontSize: 30
  36. }
  37. })
  38. this.controller.addTextSpan('地址:' + this.address,
  39. {
  40. style:
  41. {
  42. fontSize: 30
  43. }
  44. })
  45. })
  46. .copyOptions(CopyOptions.InApp)
  47. .enableDataDetector(this.enableDataDetector)
  48. .dataDetectorConfig({types : this.types, onDetectResultUpdate: (result: string)=>{}})
  49. .borderWidth(1)
  50. .padding(10)
  51. .width('100%')
  52. }
  53. .width('100%')
  54. }
  55. }
  56. }

最后,有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(Harmony NEXT)资料用来跟着学习是非常有必要的。 

这份鸿蒙(Harmony NEXT)资料包含了:鸿蒙开发必掌握的核心知识要点,内容包含了ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(Harmony NEXT)技术知识点。

希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!

获取这份完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

鸿蒙(Harmony NEXT)最新学习路线

  •  HarmonOS基础技能

  • HarmonOS就业必备技能 
  •  HarmonOS多媒体技术

  • 鸿蒙NaPi组件进阶

  • HarmonOS高级技能

  • 初识HarmonOS内核 
  • 实战就业级设备开发

有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。

获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

《鸿蒙 (OpenHarmony)开发入门教学视频》

《鸿蒙生态应用开发V2.0白皮书》

图片

《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

图片

 《鸿蒙开发基础》

  • ArkTS语言
  • 安装DevEco Studio
  • 运用你的第一个ArkTS应用
  • ArkUI声明式UI开发
  • .……

图片

 《鸿蒙开发进阶》

  • Stage模型入门
  • 网络管理
  • 数据管理
  • 电话服务
  • 分布式应用开发
  • 通知与窗口管理
  • 多媒体技术
  • 安全技能
  • 任务管理
  • WebGL
  • 国际化开发
  • 应用测试
  • DFX面向未来设计
  • 鸿蒙系统移植和裁剪定制
  • ……

图片

《鸿蒙进阶实战》

  • ArkTS实践
  • UIAbility应用
  • 网络案例
  • ……

图片

 获取以上完整鸿蒙HarmonyOS学习资料,请点击→纯血版全套鸿蒙HarmonyOS学习资料

总结

总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。 

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号