作者:闲鱼技术——光酒

本系列文章主要介绍Flutter富文本编辑的设计和实现,从协议层、渲染层、自定义扩展以及体验优化等方面,详细介绍如何实现一个功能完善、可扩展、高性能的Flutter富文本编ios是苹果还是安卓辑器,以及闲鱼在实践过程中遇到的问题和我们的一些解法。

协议篇:yuque.antfin-inc.com/guangjiu.wh… 渲染篇:yuque.antfin-inc.com/guangjiu.wh…

上一ios下载篇文嵌套分类汇总的操作步骤章,我们介绍了Felement滑板lutter富文本编辑器协议层的设计。以FlutterSlate为例,介flutter是什么意思绍了协架构图模板议层设计的几个重要的概念:嵌套Melementuiodel、Opeartion、Normalizing;站在Slate的肩膀上,让我们有了一个强壮、设计完善的富文本协议层,接下来就让我们嵌套分类汇总的操作步骤看看渲染层是如何实现的;

让我们回顾一下Mural整体的架构设计分层:

打造Flutter高性能富文本编辑器——渲染篇

渲染层主要工ios14.4.1更新了什么作是将协议Model转换嵌套成Widget渲染到屏幕上,以及处理选区、光标的计嵌套结构算和绘制,处理用户的手势交互、键盘交互等一系列工作flutter菜鸟教程

Textfield的渲染实现

首先让我们来看下Flutter的TextField是如何渲染的:

打造Flutter高性能富文本编辑器——渲染篇

p11

如上图所示,Textfield继承自StatefulWidget,会build嵌ios16套的架构师Widgelementset tree,其中有几个比较关键的Widget:架构师和程序员的区别

TextSelectionGestureDetector处理手势交互相关的逻辑,比如单击移动光标、长按选择elementanimation文字展示Toolbar等等;

另一个比较重要的Widget——EditableText;EditableText在build的时候,通过buildTextSpan方法,根据TextEditingValue的普通文本以及composing部分,创建一个Textspan对象给_Editable;最终RenderEFlutterditablelementuie通过TextPainter将文本绘制到ca嵌套结构nvas上;

Muelementaryral的渲染实现

打造Flutter高性能富文本编辑器——渲染篇

p12

如上图所示,Mural在渲染层的设计上,与原生TextField前面一部分Flutter基本是一致的,不同之处从MuralEditable开始,对应到TextField的EditableText

上面在协议层我们说了,Slate在协议在设计上是与Dom一致的,到Flutter渲染element翻译层,就会将Dom树转换成Widget tree,最终渲染到屏嵌套查询sql语句幕上;

MuralEditable不再是简单的创建一个TextSpan,而ios系统是按照Dom树结构,每一个Element映射成一个Widget;每个Element对应的Widget,创建的RenderObject实现了抽象类:架构RenderEditorInlineBox

打造Flutter高性能富文本编辑器——渲染篇

接下来我们再来看看Element对应的Widget,是怎么处理它的子节fluttering点的:

我们以最简单的EditableTextLine为例,包含Leading和flutter面试题Body两部分,Leading负责渲ios是苹果还是安卓染段落修饰相关的内容,比如有序段落的序号、引用段落前面的装饰竖线等;Body则负责渲染具体的富文本内容,实现了抽象类:RenderEditoflutter翻译rTextBox,最架构师和程序员的区别终依然将所有的叶子节点转换成InlineSpan,通过TextPainer将文本绘制到屏幕上;

打造Flutter高性能富文本编辑器——渲染篇

EditorUtilsbuildChildren方法实现如下:

打造Flutter高性能富文本编辑器——渲染篇

光标&选区渲染

光标和选区是富文本编辑器渲染层另外elementary一个需要处理的难点;

与原生TextField相比,Mural在处理光标和选区处理更加复杂;TextFelementary翻译ield所有输入文本都绘制在一个TextPainter,前面我们说过,Mural每个Element都是一个独立的段落,对应一个ReniOSderObject;在Mural中,我们需要计算用户手势操作不同段落的光标位置以及段落之间的选区计算;

要实现架构工程师Mural的光标和选区渲染,需要解决如下问题:

  1. 1. 多Element点击获取TextPoelementarysition;
  2. 2. TextPoflutter开发的app有哪些sition to MuralP嵌套查询sql语句oint;
  3. 3. 光标位置计算;

多Element点击获取TextPosition

打造Flutter高性能富文本编辑器——渲染篇

p14

如上图所示,当用户点击绿色光点位置之后,首先我们可以根据点击事件确认被点击是哪一个Elementios16所渲染的RenflutteringderObject;

首先我们通过globalToLocal方法将手嵌套函数势回调的globalPositi嵌套循环on转换为相对于Mural的localPosition;接下来遍历架构图怎么画MuralRenderEditable的child,寻找包含localPosielement翻译tion的child;

如上面介绍的,Element渲染的RenderObject实现嵌套分类汇总的操作步骤RenderEditorInlineBox抽象类,也就可以通过ge嵌套虚拟化tPositionForOffset方法获取到相对于当前TextPaiios应用商店nter的TextPelementsosition;

TextPosition to MuralPoinelementary是什么意思t

接下来就要解决第二个问题,如elementui何将TextPosition转换为协议对于光标、选区位置的描ios15述;

以上图为例,点击之后,TextPosition的Offset为12,而Slate协议是如何描述这样一elementary是什么意思个光标Flutter位置呢?如上图所示,变成了Path[0,2]offset2Point

光标位置计算

接下里就是光标位置计算,通过TextPainter的getOffsetForCaret方法,获取选中Element对应RenderObject的光标位置,然后转换成相对于Mural全局的Offset;

整体过程梳理如下:

打造Flutter高性能富文本编辑器——渲染篇

p15

支持WidgetSp架构图模板an

在实现自定义表情的过程中,我们发现在展示状态,复杂的WidgetSpan渲染是不存在问题的,但是在编辑状态支持WidgetSpan遇到了一系列问题;

简单一点的做法就是,在编辑状态将表情变成中括号包裹的文字,变成一个不可编辑的inlineflutter开发的app有哪些&void类型的Element;

但我们目标是实现一个所见即所得的富文本编辑器,为了在编辑状态支持WidgetSpan,需要解决如下几架构师和程序员的区别个问题:

  1. 1. Element到WidgetSpan渲染;
  2. 2. TextValue与Native同步问题;
  3. 3. 光标、选区TextBox计算问题;

打造Flutter高性能富文本编辑器——渲染篇

cuselement翻译tom_emoji

Element到WidgetSp架构图an渲染

我们定义了MuralCustomElement这样一个自定义Element的抽象类,如果要实现自定义表情ElFlutterement的渲染架构是什么意思,需要继承自它:

打造Flutter高性能富文本编辑器——渲染篇

其中自定义表情长度计算与Emoji不同的一点,我们认为自定义表情始终长度为一;

因为是elementary是什么意思Inline&ios是什么意思amp;Void类型,所以isInlineisVoid都返回true

TextValue与Native同步问题

Flutter文本输入组件的基本原理,就是在Native侧创建一个TextField组件,通过Tios16extInputConnection实现双端事件交互以及TextValue同步等逻辑;

当用户操作键盘进行文字的输入删除、键盘收起、移动光标等操作,会同步到Flutter侧;同样的,在Flutter进行插入、复制、手势嵌套是什么意思导致Selection变化等操作,通过调用TextInputConnectionsetEditingState同步给Natios越狱ive侧的组件;

当我们输入一个表情的时候,从Flutter角度看,我们输入了一个特殊的长度为1的字架构师和程序员的区别符,这个时候我们就需要将这个TextValue的变化同步给Native;

我们参考Placehoios14.4.1更新了什么lderSpan的实现,嵌套分类汇总的操作步骤使用字符uFFFC同步给Native;

光标、选区TextBox计算问ios16

如果我们不做任何嵌套if函数处理会发现,当包含WidgetSpan的时候,光标的位置总会计算Offset为零;深入了解代码发现问题所在:

打造Flutter高性能富文本编辑器——渲染篇

22

我们需要处理WidgetSpan的codeelementary是什么意思UnitAtVisitor以及getSpanForPositionVisitor 方法:

打造Flutter高性能富文本编辑器——渲染篇

自定义表情作为WidgetSpan的例子,其实是相对简单的;对于嵌套Widg嵌套虚拟化etSpan嵌套WidgetSpan,嵌套的WidgetSpan可flutter菜鸟教程以被选择、光标移动的场景,要怎么实现呢?大家可以想一想。

键盘交互问题

当用户键盘输入的时候,Engine侧会ios应用商店通过message channel发送TextInputClient.updateEditingState事件,将最新的TextEditingValue同步到Flutelement是什么意思ter侧;

对于TextField来说,更新的过程比较简单,整体更新TextValue即可;但对于Mural来说,每一次TextValue的更新,都进行一次TextValue到Slate Model的转换,频繁执嵌套结构行导致编辑状态下的卡顿,性能大大下降;我们采用了diffflutter翻译的方式,判断用户输入、删除内容,进而调用Commond更新Model,刷新界面渲染;

我们fluttering需要对于换行符做特殊的处理,正如之前提到过的,Element是不包含换行符的,每一次换行都会新增一个新的Element节点;

打造Flutter高性能富文本编辑器——渲染篇

另外一架构图个需要处理的问题就是移动光标的处理,如:iOS的长按移动光标、Android的横扫键盘移动光标以嵌套函数及第三方输入法移动光标的键盘操作;这里的处理方案,iOS主要是处理TextInpios14.4.1更新了什么utClient.updateFloatingCursor事件,根据Offset计算光标位置,Android以及第三方输入法的操作,主要是在TextInpios越狱utClientelementary.updateEditingState同步处理。

扩展能力

扩展能力是我们设计之初就非常重视的能力,为接入方提供简单、强大的自定义扩展能力,支持复杂、不断变化的Element业务诉求;接下来我们就以自定义主题和撤销功能的实现,来看一看Mural在扩展能力方面的设计。

自定义Node—嵌套分类汇总的操作步骤—主题能力

打造Flutter高性能富文本编辑器——渲染篇

custom_node

如上面elementary视频演示的,当输入两个#中间包含字符,则变成一个主题flutter是什么意思的样式,点击Flutter可以跳转到嵌套循环对应的主题落地页;可以对主题进行编辑,如果删掉其中一个#,则变成普通的文本。

要实现这样一个自定义主flutter菜鸟教程题,我们需要实现以下几个步骤:自定义Element、自定义Normalflutter面试题izing;

首先是定义Element:

打造Flutter高性能富文本编辑器——渲染篇

接下来就轮到强大的自定义Normalizing出场了架构图怎么制作,通过自定义规则,处理主题Node节点校验:

打造Flutter高性能富文本编辑器——渲染篇

只需要这样简单两步,就实现了主Flutter题能力fluttershy的支持;业务还可以根据自己的需求定制更加复杂的场景,比如有序段落等等。

Plugin扩展——实现撤销功能

打造Flutter高性能富文本编辑器——渲染篇

undo

如上面图所示,我们实现了elements中文翻译一个简单的Plugin层的扩展——撤销功能;在前面ios模拟器讲到协议层设计的时候,我们讨论过Slate的精简的flutteredOpeartion设计,每一次交互的Commond,最终都会拆解成一个或者多个Opeart嵌套循环ion执行;我们可以通过以下三步实现plugin的扩展:

  1. 1. 重写Operation的apply方法,通过过滤、合并等操作,记录Opeartion执行的历史;
  2. 2. 实现Opeartion的reverse方法;
  3. 3. 根据Opeartion执架构图行历史,调用Oios14.4.1更新了什么peaElementrtion的reverse方法,Element执行reverse操作;

总结

通过两篇文章,我们介绍了富文本编辑器协议层、渲染层设计和实现,完成了一个功能完善的Flutter富文本编辑器;接下来elements中文翻译我们会介绍Fluttflutteringer富文本编辑器体验优化方面闲鱼的一些实践和挑战。