ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

打造Flutter高性能富文本编辑器——协议篇

2022-10-24 14:42:02  阅读:247  来源: 互联网

标签:数据 大数据 系统


闲鱼作为一个二手闲置交易平台,卖家发布商品产出优质的供给尤为重要;商品发布器希望拥有富文本编辑能力,让用户简单便捷的方式产出更加优质的内容;Flutter本身没有富文本编辑器的能力的,只有最基础的文本编辑器TextField;对于更加复杂的场景,比如支持自定义表情、主题、有序段落等能力,目前flutter组件是无法满足我们的业务诉求,另外在交互体验上与Native仍然存在一定的差距;为了解决业务中面临的以上问题,我们决定设计并实现一个Flutter场景下高性能、可扩展的富文本编辑器。

富文本编辑器整体架构设计

首先我们来看一看整体的架构设计分层:

自下而上主要分四层:

  1. 1. 协议层:主要负责Model的定义、Selection描述、Commond事件逻辑处理,以及协议Normalizing校验;
  2. 2. 能力扩展层:能力扩展层提供丰富的plugin能力,既有内置的plugin,如:纯文本转换,undo/redo等能力,同时也非常方便的支持业务层自定义的扩展,例如支持站外H5页面展示的model to HTML的序列化plugin;
  3. 3. 渲染层:渲染层主要实现将富文本Model转换成Flutter Widget渲染,以及光标、选区、ToolBar等计算和渲染,以及用户手势交互事件等;
  4. 4. 业务扩展层:在Mural的设计之初,可扩展就是设计过程中非常重要的一部分,我们为业务方提供了非常灵活、功能强大的扩展能力,通过自定义Node、Plugin、Normalizing,实现如自定义表情、主题、段落、语法高亮等能力;

协议层设计

富文本编辑器对大家来说并不陌生,发展至今,已经涌现出非常多有优秀的开源富文本编辑器;当我们想要做Flutter富文本协议的时候,第一个想法就是先了解优秀的开源富文本编辑器方案,避免闭门造车;

目前比较优秀的开源富文本编辑器,如CKEditor、Quill、Prosemirror、Draft、Slate等等;在了解和对比过后,我们决定使用Slate作为我们的富文本编辑器的协议;

why Slatejs

我们为什么选择Slate?

插件是一等公民,能够很好的满足我们对于扩展性的要求;

Slatjs在设计上支持嵌套结构,可以满足复杂的业务场景;

与Dom相同的Data model,对于后面flutter渲染层的实现,也变得更加方便;

直观的指令设计,能够非常好的支持plugin的自定义扩展;

Slate在设计上,协议层与渲染层是有明确的核心划分,这让我们可以复用Slate协议层的设计,渲染层交给flutter来处理;

除了上面的原因,我们选择Slate另外一个很重要的原因,就是它的单元测试覆盖率和完整度,让我们对它的稳定性更有信心;

Slate协议层设计

协议层的整体架构设计如下图:

下面我们就以Slate为例,来看一看富文本编辑器的协议层设计,需要定义的核心概念和模块:

  1. 1. 嵌套Model定义;
  2. 2. 原子能力Operation设计;
  3. 3. 秩序维护者Normalizing的设计;

协议层设计——嵌套Model设计

Slate定义了三种类型的Node节点:

  • • Editor:包含整个文档内容的根节点; • Element:在自定义域中拥有语义的容器节点; • Text:包含文档文本的叶子节点;

Editor

Editor抽象接口定义如下:

Element

Element节点比较特殊,既是Ancentor节点,作为容器节点包含子节点;同时又是Descendant节点,可以作为其他容器节点的子节点存在。

  • • 块(Blocks):Element默认为Block类型的节点,也就是独立的一个段落;在Slate协议设计中,一个段落是不允许存在换行符的,当输入换行符的时候,就会生成一个新的Block类型的Element; • 行内(Inlines):同时Element也可以是Inline类型的节点,作为另外一个Element的嵌套子节点存在,作为行内元素渲染在一行; • 空元素(Void):Element也可以是Void类型,这里Void与HTML中Void的是同一个概念:如果某个Node为Void,则表示这个Node节点是不可编辑状态,光标无法定位到节点内部,会被整体输入和删除;比如:@某个人、主题、富文本中的图片或者视频等等;

Text

Text节点是树中的最低级叶子节点,描述了文本内容以及其他自定义的渲染元素;所有的自定义属性都包含在properties属性中:

我们以下面这这段富文本为例:

最终这样一段富文本对应的Mode定义如下:

可以看到,Model的树形结构还是比较简单的,所有的属性都存放在properties字段中,这也非常方便实现自定义扩展;Flutter渲染层根据Node节点的Type以及properties属性,将富文本内容渲染到屏幕上;

协议层设计——原子能力Operation

接下来需要富文本Commond协议的设计,用户的每一次的文字输入、删除、文字加粗、换行等操作都是一次Command指令;Slate抽象定义了九个最基本的Operations,协议层所有的Commond指令,最终在协议层,都会转换成一个或者多个operation操作:

  • • insert_node:插入Node节点; • insert_text:插入文本; • merge_node:合并相同属性的Node节点; • move_node:移动Node; • remove_node:删除Node; • remove_text:删除文本; • set_node:设置Node属性; • set_selection:设置Selection; • split_node:拆分Node;

下面我们通过对选中文本加粗操作为例,来了解Slate协议层Commond的处理过程:

对选中文本加粗这样一个Commond,协议层会将这个Commond拆解成三个Opeartion:

  • • split_node:将一个Text Node拆分成三个Text Node; • set_selection:更新光标选择区域Selection; • set_node:设置需要加粗Text Node节点properties的加粗属性;

当一个Commond被协议层拆分成一个或者多个Opeartion执行之后,会执行一个非常重要的操作——Normalizing;

秩序维护者——Normalizing

每一次Command操作,绝大部分情况会对Model进行相应修改;我们需要一个秩序维护者——Normalizing,时刻保证对协议Model修改过之后,保持数据结构的正确性;

Slate定义了几个基本的内置Normalizing规则:

每一次Commond之后,Editor都会调用normalizeNode方法,在Normalizing的过程中,发现存在协议结构错误,需要进行错误修复;

Normalizing的另一个强大之处在于,我们可以通过自定义Normalizing,添加自定义的校验规则,实现自定义的需求;在后面的业务扩展章节会,我们会具体讲解如何通过自定义Normalizing快速实现一个自定义主题的能力;

总结

目前Mural已经在闲鱼商品发布、商品详情、消息等场景落地,支持了自定义表情、主题等业务能力,用户体验方面也有了非常大的提升。

本次主要介绍了富文本编辑器Mural整体的架构设计以及协议层的设计;后续我们会系列文章的方式介绍Mural在渲染层的设计

 

闲鱼作为一个二手闲置交易平台,卖家发布商品产出优质的供给尤为重要;商品发布器希望拥有富文本编辑能力,让用户简单便捷的方式产出更加优质的内容;Flutter本身没有富文本编辑器的能力的,只有最基础的文本编辑器TextField;对于更加复杂的场景,比如支持自定义表情、主题、有序段落等能力,目前flutter组件是无法满足我们的业务诉求,另外在交互体验上与Native仍然存在一定的差距;为了解决业务中面临的以上问题,我们决定设计并实现一个Flutter场景下高性能、可扩展的富文本编辑器。 富文本编辑器整体架构设计 首先我们来看一看整体的架构设计分层: 自下而上主要分四层: 1. 协议层:主要负责Model的定义、Selection描述、Commond事件逻辑处理,以及协议Normalizing校验; 2. 能力扩展层:能力扩展层提供丰富的plugin能力,既有内置的plugin,如:纯文本转换,undo/redo等能力,同时也非常方便的支持业务层自定义的扩展,例如支持站外H5页面展示的model to HTML的序列化plugin; 3. 渲染层:渲染层主要实现将富文本Model转换成Flutter Widget渲染,以及光标、选区、ToolBar等计算和渲染,以及用户手势交互事件等; 4. 业务扩展层:在Mural的设计之初,可扩展就是设计过程中非常重要的一部分,我们为业务方提供了非常灵活、功能强大的扩展能力,通过自定义Node、Plugin、Normalizing,实现如自定义表情、主题、段落、语法高亮等能力; 协议层设计 富文本编辑器对大家来说并不陌生,发展至今,已经涌现出非常多有优秀的开源富文本编辑器;当我们想要做Flutter富文本协议的时候,第一个想法就是先了解优秀的开源富文本编辑器方案,避免闭门造车; 目前比较优秀的开源富文本编辑器,如CKEditor、Quill、Prosemirror、Draft、Slate等等;在了解和对比过后,我们决定使用Slate作为我们的富文本编辑器的协议; why Slatejs 我们为什么选择Slate? 插件是一等公民,能够很好的满足我们对于扩展性的要求; Slatjs在设计上支持嵌套结构,可以满足复杂的业务场景; 与Dom相同的Data model,对于后面flutter渲染层的实现,也变得更加方便; 直观的指令设计,能够非常好的支持plugin的自定义扩展; Slate在设计上,协议层与渲染层是有明确的核心划分,这让我们可以复用Slate协议层的设计,渲染层交给flutter来处理; 除了上面的原因,我们选择Slate另外一个很重要的原因,就是它的单元测试覆盖率和完整度,让我们对它的稳定性更有信心; Slate协议层设计 协议层的整体架构设计如下图: 下面我们就以Slate为例,来看一看富文本编辑器的协议层设计,需要定义的核心概念和模块: 1. 嵌套Model定义; 2. 原子能力Operation设计; 3. 秩序维护者Normalizing的设计; 协议层设计——嵌套Model设计 Slate定义了三种类型的Node节点: • Editor:包含整个文档内容的根节点; • Element:在自定义域中拥有语义的容器节点; • Text:包含文档文本的叶子节点; Editor Editor抽象接口定义如下: Element Element节点比较特殊,既是Ancentor节点,作为容器节点包含子节点;同时又是Descendant节点,可以作为其他容器节点的子节点存在。 • 块(Blocks):Element默认为Block类型的节点,也就是独立的一个段落;在Slate协议设计中,一个段落是不允许存在换行符的,当输入换行符的时候,就会生成一个新的Block类型的Element; • 行内(Inlines):同时Element也可以是Inline类型的节点,作为另外一个Element的嵌套子节点存在,作为行内元素渲染在一行; • 空元素(Void):Element也可以是Void类型,这里Void与HTML中Void的是同一个概念:如果某个Node为Void,则表示这个Node节点是不可编辑状态,光标无法定位到节点内部,会被整体输入和删除;比如:@某个人、主题、富文本中的图片或者视频等等; Text Text节点是树中的最低级叶子节点,描述了文本内容以及其他自定义的渲染元素;所有的自定义属性都包含在properties属性中: 我们以下面这这段富文本为例: 最终这样一段富文本对应的Mode定义如下: 可以看到,Model的树形结构还是比较简单的,所有的属性都存放在properties字段中,这也非常方便实现自定义扩展;Flutter渲染层根据Node节点的Type以及properties属性,将富文本内容渲染到屏幕上; 协议层设计——原子能力Operation 接下来需要富文本Commond协议的设计,用户的每一次的文字输入、删除、文字加粗、换行等操作都是一次Command指令;Slate抽象定义了九个最基本的Operations,协议层所有的Commond指令,最终在协议层,都会转换成一个或者多个operation操作: • insert_node:插入Node节点; • insert_text:插入文本; • merge_node:合并相同属性的Node节点; • move_node:移动Node; • remove_node:删除Node; • remove_text:删除文本; • set_node:设置Node属性; • set_selection:设置Selection; • split_node:拆分Node; 下面我们通过对选中文本加粗操作为例,来了解Slate协议层Commond的处理过程: 对选中文本加粗这样一个Commond,协议层会将这个Commond拆解成三个Opeartion: • split_node:将一个Text Node拆分成三个Text Node; • set_selection:更新光标选择区域Selection; • set_node:设置需要加粗Text Node节点properties的加粗属性; 当一个Commond被协议层拆分成一个或者多个Opeartion执行之后,会执行一个非常重要的操作——Normalizing; 秩序维护者——Normalizing 每一次Command操作,绝大部分情况会对Model进行相应修改;我们需要一个秩序维护者——Normalizing,时刻保证对协议Model修改过之后,保持数据结构的正确性; Slate定义了几个基本的内置Normalizing规则: 每一次Commond之后,Editor都会调用normalizeNode方法,在Normalizing的过程中,发现存在协议结构错误,需要进行错误修复; Normalizing的另一个强大之处在于,我们可以通过自定义Normalizing,添加自定义的校验规则,实现自定义的需求;在后面的业务扩展章节会,我们会具体讲解如何通过自定义Normalizing快速实现一个自定义主题的能力; 总结 目前Mural已经在闲鱼商品发布、商品详情、消息等场景落地,支持了自定义表情、主题等业务能力,用户体验方面也有了非常大的提升。 本次主要介绍了富文本编辑器Mural整体的架构设计以及协议层的设计;后续我们会系列文章的方式介绍Mural在渲染层的设计、自定义扩展设计,以及交互体验、性能方面的优化实践

标签:数据,大数据,系统
来源:

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有