接上篇:
4. 支持WidgetSpan
在实现自定义表情的过程中,我们发现在展示状态,复杂的WidgetSpan渲染是不存在问题的,但是在编辑状态支持WidgetSpan遇到了一系列问题。
简单一点的做法就是,在编辑状态将表情变成中括号包裹的文字,变成一个不可编辑的inline&void类型的Element。
但我们目标是实现一个所见即所得的富文本编辑器,为了在编辑状态支持WidgetSpan,需要解决如下几个问题:
• Element到WidgetSpan渲染。
• TextValue与Native同步问题。
• 光标、选区TextBox计算问题。
1) Element到WidgetSpan渲染
我们定义了MuralCustomElement这样一个自定义Element的抽象类,如果要实现自定义表情Element的渲染,需要继承自它:
其中自定义表情长度计算与Emoji不同的一点,我们认为自定义表情始终长度为一。因为是Inline&Void类型,所以isInline和isVoid都返回true。
2) TextValue与Native同步问题
Flutter文本输入组件的基本原理,就是在Native侧创建一个TextField组件,通过TextInputConnection实现双端事件交互以及TextValue同步等逻辑。
当用户操作键盘进行文字的输入删除、键盘收起、移动光标等操作,会同步到Flutter侧;同样的,在Flutter进行插入、复制、手势导致Selection变化等操作,通过调用TextInputConnection的setEditingState同步给Native侧的组件。
当我们输入一个表情的时候,从Flutter角度看,我们输入了一个特殊的长度为1的字符,这个时候我们就需要将这个TextValue的变化同步给Native。
我们参考PlaceholderSpan的实现,使用字符\uFFFC同步给Native。
3) 光标、选区TextBox计算问题
如果我们不做任何处理会发现,当包含WidgetSpan的时候,光标的位置总会计算Offset为零;深入了解代码发现问题所在:
我们需要处理WidgetSpan的codeUnitAtVisitor以及getSpanForPositionVisitor 方法:
自定义表情作为WidgetSpan的例子,其实是相对简单的;对于WidgetSpan嵌套WidgetSpan,嵌套的WidgetSpan可以被选择、光标移动的场景,要怎么实现呢?大家可以想一想。
5. 键盘交互问题
当用户键盘输入的时候,Engine侧会通过message channel发送TextInputClient.updateEditingState事件,将最新的TextEditingValue同步到Flutter侧。
对于TextField来说,更新的过程比较简单,整体更新TextValue即可;但对于Mural来说,每一次TextValue的更新,都进行一次TextValue到Slate Model的转换,频繁执行导致编辑状态下的卡顿,性能大大下降;我们采用了diff的方式,判断用户输入、删除内容,进而调用Commond更新Model,刷新界面渲染。
我们需要对于换行符做特殊的处理,正如之前提到过的,Element是不包含换行符的,每一次换行都会新增一个新的Element节点。
另外一个需要处理的问题就是移动光标的处理,如:iOS的长按移动光标、Android的横扫键盘移动光标以及第三方输入法移动光标的键盘操作;这里的处理方案,iOS主要是处理TextInputClient.updateFloatingCursor事件,根据Offset计算光标位置,Android以及第三方输入法的操作,主要是在TextInputClient.updateEditingState同步处理。
接下篇:https://developer.aliyun.com/article/1225969?groupCode=idlefish