未验证 提交 a1a100c6 编写于 作者: O openharmony_ci 提交者: Gitee

!23622 add richEditor selectionMenu and copyOption content

Merge pull request !23622 from lukewang1/cherry-pick-1693475574
......@@ -33,9 +33,9 @@ RichEditor(value: RichEditorOptions)
| 名称 | 参数类型 | 描述 |
| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| customKeyboard<sup>10+</sup> | [CustomBuilder](ts-types.md#custombuilder8) | 设置自定义键盘。<br/>**说明:**<br/>当设置自定义键盘时,输入框激活后不会打开系统输入法,而是加载指定的自定义组件。<br/>自定义键盘的高度可以通过自定义组件根节点的height属性设置,宽度不可设置,使用系统默认值。<br/>自定义键盘采用覆盖原始界面的方式呈现,不会对应用原始界面产生压缩或者上提。<br/>自定义键盘无法获取焦点,但是会拦截手势事件。<br/>默认在输入控件失去焦点时,关闭自定义键盘。 |
| customKeyboard | [CustomBuilder](ts-types.md#custombuilder8) | 设置自定义键盘。<br/>**说明:**<br/>当设置自定义键盘时,输入框激活后不会打开系统输入法,而是加载指定的自定义组件。<br/>自定义键盘的高度可以通过自定义组件根节点的height属性设置,宽度不可设置,使用系统默认值。<br/>自定义键盘采用覆盖原始界面的方式呈现,不会对应用原始界面产生压缩或者上提。<br/>自定义键盘无法获取焦点,但是会拦截手势事件。<br/>默认在输入控件失去焦点时,关闭自定义键盘。 |
| bindSelectionMenu | {<br/>spantype:&nbsp;[RichEditorSpanType](#richeditorspantype),<br/>content:&nbsp;[CustomBuilder](ts-types.md#custombuilder8),<br/>responseType:&nbsp;[ResponseType](ts-appendix-enums.md#responsetype8),<br/>options?:&nbsp;[SelectionMenuOptions](#selectionmenuoptions)<br/>} | 设置自定义选择菜单。<br/> 默认值:{<br/> spanType:&nbsp;RichEditorSpanType:TEXT<br/>responseType:&nbsp;ResponseType.LongPress<br/>其他:空<br/>}<br/>**说明:**<br/>当前spanType参数设置不会生效,不区分类型。|
| copyOption | [CopyOptions](ts-appendix-enums.md#copyoptions9) | 组件支持设置内容是否可复制粘贴。<br />默认值:CopyOptions.LocalDevice <br/>**说明:** <br/>设置copyOptions为CopyOptions.InApp或者CopyOptions.LocalDevice,长按组件内容,会弹出文本默认选择菜单,可选中内容并进行复制、全选操作。<br/>设置copyOptions为CopyOptions.None,复制、剪切功能不生效。 |
## 事件
除支持[通用事件](ts-universal-events-click.md)外,还支持以下事件:
......@@ -100,6 +100,16 @@ Span位置信息。
| spanIndex | number | 是 | Span索引值。 |
| spanRange | [number, number] | 是 | Span内容在RichEditor内的起始和结束位置。 |
## RichEditorSpanType
Span类型信息。
| 名称 | 类型 | 必填 | 说明 |
| -------- | -------- | -------- | -------- |
| TEXT | number | 是 | Span为文字类型。 |
| IMAGE | number | 是 | Span为图像类型。|
| MIXED | number | 是 | Span为图文混合类型。|
## RichEditorTextStyleResult
......@@ -257,6 +267,11 @@ deleteSpans(value?: RichEditorRange): void
| ------ | -------- | ---- | -------------------------------------- |
| value | [RichEditorRange](#richeditorrange) | 否 | 删除范围。省略时,删除所有文本和图片。|
### closeSelectionMenu
closeSelectionMenu(): void
关闭自定义选择菜单或系统默认选择菜单。
## RichEditorSelection
......@@ -341,6 +356,15 @@ deleteSpans(value?: RichEditorRange): void
| start | number | 否 | 起始位置,省略或者设置负值时表示从0开始。 |
| end | number | 否 | 结束位置,省略或者超出文本范围时表示到结尾。 |
## SelectionMenuOptions
范围信息。
| 名称 | 类型 | 必填 | 描述 |
| ------ | -------- | ---- | -------------------------------------- |
| onAppear | ?(() => void) | 否 | 自定义选择菜单弹出时回调。 |
| onDisappear | ?(() => void) | 否 | 自定义选择菜单关闭时回调。 |
## 示例
......@@ -539,3 +563,361 @@ struct RichEditorExample {
```
![customKeyboard](figures/richEditorCustomKeyboard.png)
### 示例3
```ts
// xxx.ets
import pasteboard from '@ohos.pasteboard'
@Entry
@Component
struct SelectionMenu {
@State message: string = 'Hello World'
@State textSize: number = 40
@State sliderShow: boolean = false
@State start: number = -1
@State end: number = -1
@State colorTransparent: Color = Color.Transparent
controller: RichEditorController = new RichEditorController();
options: RichEditorOptions = { controller: this.controller }
private iconArr: Array<Resource> =
[$r('app.media.icon'), $r("app.media.icon"), $r('app.media.icon'),
$r("app.media.icon"), $r('app.media.icon')]
private listArr: Array<{
imageSrc: Resource,
id: string,
label: string
}> =
[{ imageSrc: $r('sys.media.ohos_ic_public_cut'), id: '剪切', label: "Ctrl+X" },
{ imageSrc: $r('sys.media.ohos_ic_public_copy'), id: '复制', label: "Ctrl+C" },
{ imageSrc: $r('sys.media.ohos_ic_public_paste'), id: '粘贴', label: "Ctrl+V" },
{ imageSrc: $r('sys.media.ohos_ic_public_select_all'), id: '全选', label: "Ctrl+A" },
{ imageSrc: $r('sys.media.ohos_ic_public_share'), id: '分享', label: "" },
{ imageSrc: $r('sys.media.ohos_ic_public_translate_c2e'), id: '翻译', label: "" },
{ imageSrc: $r('sys.media.ohos_ic_public_search_filled'), id: '搜索', label: "" }]
@State iconBgColor: ResourceColor[] = new Array(this.iconArr.length).fill(this.colorTransparent)
@State listBgColor: ResourceColor[] = new Array(this.listArr.length).fill(this.colorTransparent)
@State iconIsFocus: boolean[] = new Array(this.iconArr.length).fill(false)
@State listIsFocus: boolean[] = new Array(this.iconArr.length).fill(false)
@State clickWeightNum: number = 0
@State clickNum: number[] = [0, 0, 0]
build() {
Column() {
Column() {
RichEditor(this.options)
.onReady(() => {
this.controller.addTextSpan(this.message, { style: { fontColor: Color.Orange, fontSize: 30 } })
})
.onSelect((value: RichEditorSelection) => {
if (value.selection == [-1. - 1]) return
[this.start, this.end] = value.selection
})
.bindSelectionMenu(RichEditorSpanType.TEXT, this.panel(), ResponseType.LongPress, { onDisappear: () => {
this.sliderShow = false
}})
.borderWidth(1)
.borderColor(Color.Red)
.width(200)
.height(200)
.position({ x: 150, y: 100 })
}.width('100').backgroundColor(Color.White)
}.height('100')
}
@Builder
panel() {
Column() {
Menu() {
MenuItem({ builder: this.iconPanel() })
}.shadow(ShadowStyle.OUTER_DEFAULT_MD).margin({ bottom: 8 })
Menu() {
if (!this.sliderShow) {
MenuItem({ builder: this.listPanel() })
} else {
MenuItem({ builder: this.sliderPanel() })
}
}
.backgroundColor(Color.Transparent).focusable(true).shadow(ShadowStyle.OUTER_DEFAULT_MD)
}
}
@Builder iconPanel() {
Column() {
Row({ space: 2 }) {
ForEach(this.iconArr, (item, index) => {
Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
Image(item).fillColor($r('sys.color.ohos_id_color_primary')).width(24).height(24).focusable(true)
}
.border({ width: this.iconIsFocus[index] ? 2 : 0, color: $r('sys.color.ohos_id_color_focused_outline') })
.borderRadius($r('sys.float.ohos_id_corner_radius_default_m'))
.width(48)
.height(48)
.focusable(true)
.focusOnTouch(true)
.onClick(() => {
if (index == 0) {
this.clickNum[0]++
this.sliderShow = false
this.controller.updateSpanStyle({ start: this.start, end: this.end, textStyle: {
fontWeight: this.clickNum[0] % 2 !== 0 ? FontWeight.Bolder : FontWeight.Normal
} })
} else if (index == 1) {
this.clickNum[1]++
this.sliderShow = false
this.controller.updateSpanStyle({ start: this.start, end: this.end, textStyle: {
fontStyle: this.clickNum[1] % 2 !== 0 ? FontStyle.Italic : FontStyle.Normal
} })
} else if (index == 2) {
this.clickNum[2]++
this.sliderShow = false
this.controller.updateSpanStyle({ start: this.start, end: this.end, textStyle: {
decoration: {
type: this.clickNum[2] % 2 !== 0 ? TextDecorationType.Underline : TextDecorationType.None
} } })
} else if (index == 3) {
this.sliderShow = !this.sliderShow
} else if (index == 4) {
this.sliderShow = false
}
})
.onTouch((event: TouchEvent) => {
if (event.type === TouchType.Down) {
this.iconBgColor[index] = $r('sys.color.ohos_id_color_click_effect')
}
if (event.type === TouchType.Up) {
this.iconBgColor[index] = this.colorTransparent
}
})
.onHover((isHover: boolean) => {
this.iconBgColor.forEach((icon, index1) => {
this.iconBgColor[index1] = this.colorTransparent
})
isHover ? this.iconBgColor[index] = $r('sys.color.ohos_id_color_hover') :
this.listBgColor[index] = this.colorTransparent
})
.onFocus(() => {
this.iconIsFocus[index] = true
})
.onBlur(() => {
this.iconIsFocus[index] = false
})
.backgroundColor(this.iconBgColor[index])
})
}
}
.backgroundColor(this.colorTransparent)
.borderRadius($r('sys.float.ohos_id_corner_radius_card'))
.width(256)
.height(56)
.padding(4)
}
@Builder listPanel() {
Column() {
List({ space: 0, initialIndex: 0 }) {
ForEach(this.listArr, (item, index) => {
ListItem() {
listChild({
item,
index,
listBgColor: $listBgColor,
colorTransparent: $colorTransparent
})
.onClick(() => {
let sysBoard = pasteboard.getSystemPasteboard()
this.controller.closeSelectionMenu()
let pasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, '')
this.controller.getSpans({ start: this.start, end: this.end })
.forEach((item, i) => {
if ("imageStyle" in item) {
var style = item.imageStyle
let data = pasteboard.createRecord(pasteboard.MIMETYPE_PIXELMAP, item.valuePixelMap)
let prop = pasteData.getProperty()
prop.additions[i] = { 'width': style.size[0], 'height': style.size[1], 'fit': style.objectFit }
pasteData.addRecord(data)
pasteData.setProperty(prop)
} else {
let style = item.textStyle
let data = pasteboard.createRecord(pasteboard.MIMETYPE_TEXT_PLAIN, item.value)
let prop = pasteData.getProperty()
prop.additions[i] = { 'color': style.fontColor, 'size': style.fontSize, 'style': style.fontStyle,
'weight': style.fontWeight }
pasteData.addRecord(data)
pasteData.setProperty(prop)
}
})
switch (index) {
case 0:
this.controller.deleteSpans({ start: this.start, end: this.end })
case 1:
sysBoard.clearData()
sysBoard.setData(pasteData).then(() => {
console.info('Succeeded in setting PasteData.');
}).catch((err) => {
console.error('Failed to set PasteData. Cause: ' + err.message);
})
break
case 2:
sysBoard.getData((err, data) => {
if (err) {
return
}
var count = data.getRecordCount()
for (let m = 0; m < count; m++) {
const element = data.getRecord(m);
let tex: RichEditorTextStyle = {
fontSize: 30,
fontColor: Color.Orange,
fontWeight: FontWeight.Normal
}
let im: RichEditorImageSpanStyle = { objectFit: ImageFit.Contain, size: [50, 50] }
if (data.getProperty().additions[m]) {
const entry = Object.entries(data.getProperty().additions[m])
for (let [key, value] of entry) {
switch (key) {
case 'width':
im.size[0] = value
continue
case 'height':
im.size[1] = value
continue
case 'fit':
im.objectFit = value
continue
case 'color':
tex.fontColor = value
continue
case 'size':
tex.fontSize = value
continue
case 'style':
tex.fontStyle = value
continue
case 'weight':
tex.fontWeight = value
}
}
}
if (element.mimeType == pasteboard.MIMETYPE_TEXT_PLAIN) {
this.controller.addTextSpan(element.plainText,
{
style: tex,
offset: this.controller.getCaretOffset()
})
}
if (element.mimeType == pasteboard.MIMETYPE_PIXELMAP) {
this.controller.addImageSpan(element.pixelMap,
{
imageStyle: im,
offset: this.controller.getCaretOffset()
})
}
}
})
break
case 3: // 全选
}
})
}
.height(48)
.borderRadius($r('sys.float.ohos_id_corner_radius_card'))
.focusable(true)
.focusOnTouch(true)
.border({ width: this.listIsFocus[index] ? 2 : 0, color: $r('sys.color.ohos_id_color_focused_outline') })
.onFocus(() => {
this.listIsFocus[index] = true
})
.onBlur(() => {
this.listIsFocus[index] = false
})
}, item => item)
}
}
.focusable(true)
.width(256)
.padding(4)
.backgroundColor(this.colorTransparent)
.borderRadius($r('sys.float.ohos_id_corner_radius_card'))
}
@Builder sliderPanel() {
Column() {
Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
Text('A').fontSize(15)
Slider({ value: this.textSize, step: 10, style: SliderStyle.InSet })
.width(210)
.onChange((value: number, mode: SliderChangeMode) => {
this.textSize = value
this.controller.updateSpanStyle({ start: this.start, end: this.end, textStyle: { fontSize: this.textSize }
})
})
Text('A').fontSize(20).fontWeight(FontWeight.Medium)
}.borderRadius($r('sys.float.ohos_id_corner_radius_card'))
}
.backgroundColor(this.colorTransparent)
.borderRadius($r('sys.float.ohos_id_corner_radius_card'))
.padding(15)
.width(256)
.height(56)
.margin({ bottom: 8 })
}
}
@Component
struct listChild {
item
index
@Link listBgColor: (Resource | Color)[]
@Link colorTransparent: Resource
build() {
Column() {
Flex({
direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center
}) {
Row() {
Image(this.item.imageSrc)
.width(20)
.height(20)
.margin({ right: 8 })
.fillColor($r('sys.color.ohos_id_color_primary'))
.focusable(true)
Text('' + this.item.id)
.textAlign(TextAlign.Center)
.borderRadius(10)
.focusable(true)
.fontColor($r('sys.color.ohos_id_color_primary'))
.fontSize($r('sys.float.ohos_id_text_size_body1'))
}
Row() {
Text('' + this.item.label)
.fontColor($r('sys.color.ohos_id_color_text_secondary')).fontSize($r('sys.float.ohos_id_text_size_body1'))
}
}
.onTouch((event: TouchEvent) => {
if (event.type === TouchType.Down) {
this.listBgColor[this.index] = $r('sys.color.ohos_id_color_click_effect')
}
if (event.type === TouchType.Up) {
this.listBgColor[this.index] = this.colorTransparent
}
})
.onHover((isHover: boolean) => {
this.listBgColor[this.index] = isHover ? $r('sys.color.ohos_id_color_hover') : this.colorTransparent
})
.backgroundColor(this.listBgColor[this.index])
.padding({ right: 12, left: 12 })
.height('48')
.focusable(true)
.borderRadius($r('sys.float.ohos_id_corner_radius_default_m'))
}
}
}
```
![selectionMenu](figures/richEditorSelectionMenu.png)
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册