RichEditor是支持图文混排和文本交互式编辑的组件,通常用于响应用户对图文混合内容的输入操作,例如可以输入图文的评论区。
开发者可以创建基于属性字符串进行内容管理的RichEditor组件或创建基于Span进行内容管理的RichEditor组件。
该组件从API version 10开始支持。具体用法参考 RichEditor。
RichEditorController控制器
-
addTextSpan
添加文本内容,如果组件光标闪烁,插入后光标位置更新为新插入文本的后面。
-
addImageSpan
添加图片内容,如果组件光标闪烁,插入后光标位置更新为新插入图片的后面。
不建议直接添加网络图片。
-
addBuilderSpan
在RichEditor中添加用户自定义布局(BuilderSpan)。
说明:RichEditor组件添加占位Span,占位Span调用系统的measure方法计算真实的长宽和位置。 可通过RichEditorBuilderSpanOptions设置此builder在RichEditor中的index(一个文字为一个单位)。 此占位Span不可获焦,支持拖拽,支持部分通用属性,占位、删除等能力等同于ImageSpan,长度视为一个文字。 支持通过bindSelectionMenu设置自定义菜单。 不支持通过getSpans,getSelection,onSelect,aboutToDelete获取builderSpan信息。 不支持通过updateSpanStyle,updateParagraphStyle等方式更新builder。 对此builder节点进行复制或粘贴不生效。 builder的布局约束由RichEditor传入,如果builder里最外层组件不设置大小,则会用RichEditor的大小作为maxSize。 builder的手势相关事件机制与通用手势事件相同,如果builder中未设置透传,则仅有builder中的子组件响应。 如果组件光标闪烁,插入后光标位置更新为新插入builder的后面。
-
addSymbolSpan
在RichEditor中添加图标小符号(SymbolSpan),如果组件光标闪烁,插入后光标位置更新为新插入SymbolSpan的后面。暂不支持手势、复制、拖拽处理。
自定义BuidlerSpan填坑
通过addBuilderSpan接口添加的自定义布局Span,getSpans、onWillChange等API不会返回BuilderSpan内部的信息。开发者需要自行维护BuilderSpan的状态,并且在组件内容发生变化时同步更新。
官方示例:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-richeditor#示例10使用和管理组件内的builderspan
官方组件默认只支持TextSpan、ImageSpan、SymbolSpan返回信息,通过addBuilderSpan接口添加的无法获取。示例中代码逻辑比较复杂,经过多方调研想到了扩展RichEditorController控制器的方法,可以完全根据自己的业务来自定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| import { componentUtils } from '@kit.ArkUI' interface CustomSpanInfo { id: string componentInfo: componentUtils.ComponentInfo } const BUILDERSPAN_ID = 'customSpanId' export class MyRichEditorController extends RichEditorController { private customBuilderIds: string[] = [] generateBuilderId() { return `customSpan${this.getCustomBuilderSize()}` } getCustomBuilderSize() { return this.customBuilderIds.length } addCustomBuilderSpan(id: string, value: CustomBuilder, options?: RichEditorBuilderSpanOptions | undefined): number { this.customBuilderIds.push(id) return super.addBuilderSpan(value, options) } getSpans(value?: RichEditorRange | undefined): (RichEditorImageSpanResult | RichEditorTextSpanResult)[] { let customSpanInfos: CustomSpanInfo[] = [] for (let id of this.customBuilderIds) { let componentInfo = componentUtils.getRectangleById(id) if (!componentInfo) { continue } // 宽高为0代表已经被删除了 if (componentInfo.size.width == 0 && componentInfo.size.height == 0) { continue } customSpanInfos.push({ id, componentInfo }) } // 根据组件位置排序 customSpanInfos = customSpanInfos.sort((a, b) => { if (a.componentInfo.windowOffset.y >= b.componentInfo.windowOffset.y + b.componentInfo.size.height) { return 1 } if (a.componentInfo.windowOffset.y + a.componentInfo.size.height <= b.componentInfo.windowOffset.y) { return -1 } return a.componentInfo.windowOffset.x - b.componentInfo.windowOffset.x }) // 按序替换得到最终结果 let spans = super.getSpans(value) let index = 0 for (let span of spans) { if (this.isCustomSpan(span)) { span[BUILDERSPAN_ID] = customSpanInfos[index++].id } } return spans } /** 判断是否CustomSpan */ isCustomSpan(span: RichEditorTextSpanResult | RichEditorImageSpanResult): boolean { return span['imageStyle'] != undefined && (span['valueResourceStr'] == '' || span['valueResourceStr'] == ' ') } /** 获取CustomSpanId */ getCustomSpanId(span: RichEditorTextSpanResult | RichEditorImageSpanResult): string | undefined { return span[BUILDERSPAN_ID] } /** 清空 */ clear() { this.customBuilderIds = [] this.deleteSpans() } }
|
getSpan填坑
getSpan是用于获取span信息。参数是start和end即起始位置和结束位置,最好都为-1则返回所有span,若end使用getCaretOffset则只会返回到当前光标所在位置。
上传文件填坑
一般上传文件代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const formData = new FormData(); formData.append('file', this.selectedFile);
axios.post('YOUR_UPLOAD_URL', formData, { headers: { 'Content-Type': 'multipart/form-data' } }) .then(response => { this.uploadStatus = '文件上传成功'; }) .catch(error => { this.uploadStatus = '文件上传失败: ' + error.message; });
|
但是鸿蒙却不支持只有,上传上去后端却说什么也没有收到而且当前版本只支持 Stage 模型。具体参考 OpenHarmony-SIG / ohos_axios。
鸿蒙代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| const formData = new FormData(); let selectedFile = fs.openSync(path,fs.OpenMode.READ_ONLY)
let stat = fs.lstatSync(selectedFile.path) let buffer = new ArrayBuffer(stat.size) fs.readSync(selectedFile.fd,buffer) fs.fsyncSync(selectedFile.fd) formData.append('files', buffer,{ filename:selectedFile.name, type: 'image/png' }) fs.closeSync(selectedFile.fd) axios.post('YOUR_UPLOAD_URL', formData, { headers: { 'Content-Type': 'multipart/form-data' } }) .then(response => { this.uploadStatus = '文件上传成功'; }) .catch(error => { this.uploadStatus = '文件上传失败: ' + error.message; });
|
文件相关api尽量是文件描述符,使用文件对象可能发生异常。添加文件时必须传文件类型,否则无法查看文件。
其他避坑指南
嵌套类数据更新UI不更新怎么办?
正常按照官网来的话使用v1和v2状态管理基本上没啥问题,涉及到复杂逻辑带有嵌套类的话可能会遇到UI不更新的问题,原因是通过服务端返回json解析而来的实体类会无效。
解决办法如下:
1 2 3 4 5 6 7 8 9 10 11
| axios.create({ ... transformResponse:[(data:object):object = >{ try{ UIUtils.makeObserved(JSON.parse(data)) }catch(err){ return UIUtils.makeObserved(data) } }] })
|
鸿蒙打包hvigorw clean报错No npmrc file is matched in the current user folder解决?
1 2
| registry=https: @ohos:registry=https:
|
Image组件无法响应长按事件?
组件默认拖拽效果,设置为true时,组件可拖拽,绑定的长按手势不生效。API version 9及之前,默认值为false。API version
10及之后,默认值为true。
1 2
| Image() .draggable(fasle)
|
@LocalBuilder和@Builder区别?
@Builder:动态绑定到当前组件上下文(通常是子组件),传递到子组件时函数内部this指向子组件实例;可以使用wrapBuilder包裹全局Buidler,通过对象封装避免上下文丢失。
@LocalBuilder:保留原始父组件上下文,传递到子组件时函数内this仍指向父组件实例。
安全区域使用expandSafeArea属性无效?
最好同时设置固定宽高和expandSafeArea属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Entry @Component struct SafeAreaExample1 { @State text: string = '' controller: TextInputController = new TextInputController()
build() { Row() { Column() .height('100%').width('100%') .backgroundImage($r('app.media.bg')).backgroundImageSize(ImageSize.Cover) .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) }.height('100%') } }
|
跨平台路由管理?
推荐使用@ohos/router
状态数据监听?
推荐使用@Observe/@ObserveV2/UIUtils.makeObserved
常见问题, 行业实践与常见问题