文本编辑在Flutter中的工作方式

本文翻译自
原文地址:How text editing works internally in Flutter
原作者:Suragch

【个人阅读心得】这篇文章由表及里介绍了Flutter中文本绘制和编辑的处理流程,讲了各个层级所负责的内容,能够帮助读者对文本处理有个大致认知,原来文本绘制和编辑在底层是很复杂的系统,包括如何和系统键盘打交道,怎样布局输入框里的内容,以及怎么处理和底层框架交互等。想要更深入的理解,可以一步一步按照文章中介绍的层级去阅读源码,此外文中的引文Flutter文本渲染对渲染过程进行了详细讲解,非常推荐延伸阅读一下。得益于这篇文章中的Text and selection一节,我们的小伙伴优化了TextField输入精度计算,解决了中文输入最大限制个数计算不准确的困扰。此外,翻译不准确的地方欢迎大家指正。


写给那些喜欢钻研细节的人。

文本编辑在Flutter中的工作方式_第1张图片
hellowo01.png

由于Flutter仅支持水平文本布局,因此在为了给传统蒙古文字创建垂直布局的widget时,我不得不深入研究文本渲染系统的底层源码。在本文中,我将分享我发现的,关于Flutter中文本编辑widget工作方式的知识。

文本渲染(Text rendering)

通常我们看到的关于Text widget是这样子的:

Text(
  'Hello world',
  style: TextStyle(fontSize: 30),
),

然而,它的下面还有很多层级。

Widget层(Widgets layer)

当你使用Textwidget时,它实际创建的是一个RichText widget。

文本编辑在Flutter中的工作方式_第2张图片
text_richtext02.png

与Text(将String作为数据参数)不同,RickText采用TextSpan(更准确的说是InlineSpan, 稍后会介绍更多相关内容),TextSpan需要String和一个TextStyle作为参数,因此在Text创建RichText之前,它需要你提供任意TextStyle并将其与APP默认的TextStyle组合,该默认TextStyle是从BuildContext中获取的,下面是上图的更详细版本:

文本编辑在Flutter中的工作方式_第3张图片
richtext03.png

由于Text仅采用单个字符串和style样式作为参数,因此它在TextSpan中提供给RichText的字符串只有一种样式,但是TextSpan很有趣,就像多子节点widget一样,它能将更多的文本对象作为子节点。这意味着你可以使用它来构建整个文本树,其中每个节点都可以有各自的字符串和样式,

文本编辑在Flutter中的工作方式_第4张图片
textspan04.png

但是这很难推导出来,因为通常你只有一个父TextSpan和一堆子节点

文本编辑在Flutter中的工作方式_第5张图片
textspan05.png

你可以将TextSpan对象传递给Text.rich构造函数,或者直接传递给TextRich widget,但是,如果将其直接传递给TextRich widget的话,该样式则不会和build context中的默认样式组合。

阅读掌握Flutter中的文本样式一文,以获得使用TextSpan设置文本样式的实际示例。

RichText本身是MultiChildRenderObjectWidget的子类,RichText内部有多个子节点的原因是,你可以在一行的文本内部散布其他的widget,上面的图片显示的是一个TextSpan树,但实际上RichText内置的是InlineSpan,InlineSpan既可以是TextSpan也可以是WidgetSpan,它是他们的父类。出于本文的目的,我将仅处理TextSpan。

渲染层(Rendering layer)

Widget只是最终用于创建RenderObject的蓝图,RichText创建的渲染对象叫做RenderParagraph

文本编辑在Flutter中的工作方式_第6张图片
renderParagraph06.png

RenderParagraph会计算文本的大小,当然也会处理其他事情,比如命中测试以及它的任何子widget的布局。

绘制层(Painting layer)

RenderParagraph并不直接绘制文本,而是创建一个TextPainter来管理这些工作。

文本编辑在Flutter中的工作方式_第7张图片
textpainter07.png

与他的名字相反,TextPainter实际上也不绘制文本,但是它会管理周围所有绘制相关的工作,包括要绘制的画布Canvas以及从TextSpan树上提取样式和字符串。

此外,TextSpan还可以处理插入符的的位置,这对于Text widget不那么重要,但是对于接下来要讲的文本编辑将会很重要。

基础层(Foundation layer)

每个人都以为穿过重重困难,即将真正绘制文本的时候,实际则不止于此。

在Flutter框架的最底层,你将会看到一个ParagraphBuilder和一个Paragraph对象,TextPainter内部会创建ParagraphBuilder,而ParagraphBuilder又会用于生成Paragraph。

文本编辑在Flutter中的工作方式_第8张图片
paragraph08.png

ParagraphBuilder中包含一点逻辑,但是Paragraph几乎不包含任何逻辑,这两个对象将大部分工作传给Flutter engine。

Flutter引擎层(Flutter engine layer)

Flutter引擎是用C/C++写的,因此对于使用Dart的Flutter开发者,通常不会直接使用,处理文本绘制的库叫做LibTxt。

文本编辑在Flutter中的工作方式_第9张图片
libtxt09.png

LibTxt当前已经被Skia的SkParagraph模块替换,可以关注这个issue跟踪进展。

总结

下面的图片展示了渲染工作的所有图层

文本编辑在Flutter中的工作方式_第10张图片
layer10.png

阅读Flutter文本渲染这篇文章,深入了解以上内容。【译者按:强烈推荐阅读,之后也会翻译出来或者总结学习笔记】

文本编辑(Text editing)

我的一个问题是文本渲染和文本编辑在架构层上有多少是共享的,答案是从TextPainter向下的所有内容,这就很方便了,意味着我们只需要了解它上层的内容。

Material和Cupertino层(Material and Cupertino layers)

当你想要在Flutter中输入和编辑的时候,TextField可能是用户首先想到的widghet。

TextField(
  decoration: InputDecoration(
    hintText: 'Search',
  ),
),

然而,TextField只是组成Material库的一部分,这是widget层更上的一层,与之对应的在Cupertino库中widget称为CupertinoTextField

文本编辑在Flutter中的工作方式_第11张图片
textfield11.png

假如你使用TextField.adaptive构造函数的话,它将会在iOS和macOS创建CupertinoTextField,但是在其它平台创建TextField。

文本编辑在Flutter中的工作方式_第12张图片
textfieldadaptive12.png

你可能也想知道TextFormField,但这仅是一个TextField,包装了一些逻辑,使保存和验证更加容易。据我所知,没有CupertinoTextFormField。

与Text这种无状态(stateless)widget不同,TextField和CupertinoTextField是有状态的(statefull)widget。这是因为他们需要持续跟踪诸如TextEditingController、焦点(focus)、鼠标悬停(mouse hovering)、手势(gestures)等之类的东西。

无论使用哪种,TextField和Cupertino TextField都将最终创建一个EditableText widget。

文本编辑在Flutter中的工作方式_第13张图片
editabletext13.png

然后我们就进入到了widget层。

Widget层(Widgets layer)

在这一层,你将不会再具有某些上一层提供的功能,例如占位符文本和标签,不过这里有很多其他属性。

EditableText管理文本和选择区域,与键盘、光标和滚动事件进行通信。

文本和选择区域(Text and selection)

你可能之前使用过TextEditingController,尽管它本身并不是一个widget,但是它属于widget层,用来处理EditableText。继承自ValueNotifier,当TextEditingValue发生改变的时候,通知它的监听者。

TextEditingValue对象有三个部分组成,文本、选择区域和编辑区域(text, selection, and composing)。

文本编辑在Flutter中的工作方式_第14张图片
texteditingvalue14.png
  • text:这里是用户已经输入的任何字符串。
  • selection:这是一个TextSelection对象,通过它你可以知道当前所选择的光标位置和选择范围,除了选择的开始和结束值外,TextSelection也包含文本方向和光标在换行处的精确位置。
  • composing:这是你正在编辑单词的TextRange(仅包含开始和结束的偏移量)。你知道当你在输入某些内容的时候,键盘会提出一些建议吗?如果你选择键盘建议,则带下划线的文本将会被你选择的键盘建议文本替换掉。
文本编辑在Flutter中的工作方式_第15张图片
composing15.png

与系统键盘进行通信(Communicating with the system keyboard)
当弹出系统软键盘时,它属于底层系统,Flutter的Editable widget需要一种与该系统通信的方式,无论是获取信息(用户使用键盘输入)还是发送信息(如更改选择区域,或者让键盘消失)。

下面是架构示意图:


文本编辑在Flutter中的工作方式_第16张图片
keyboard16.png
  • EditableText实现TextInputClient,当用户使用系统键盘输入文本时,它可以接收更新。
  • TextInputClient创建一个TextInputConnection对象,它是一个用于将消息发送给系统键盘的接口。
  • 异步消息传递全部通过底层的TextInput服务,该服务通过平台channel与底层系统进行通信。
  • 在native侧,plugin插件将会处理往返于TextInput的消息,每个平台都有自己唯一的插件,用于Android系统的Android插件,用于iOS系统的iOS插件,该插件将与本机系统输入控件进行通信(比如键盘)。
  • Flutter端和插件端各自维护自己的TextEditingValue版本,并且需要保持同步。

光标(Cursor)
EditableText使用了两个AnimationController对象为光标设置动画。一个用于标准的光标闪烁(通过动画的透明度实现),另一个用于浮动光标,这时iOS系统中的标准样式,你可以在下面视频中看到

文本编辑在Flutter中的工作方式_第17张图片
cursor17.png

滚动(Scrolling)
在EditableText的build方法中,内容由Scrollable widget进行包裹了一层,这允许文本垂直滚动以显示多行文本,水平滚动以显示单行文本。传入ScrollController可以进行进一步的定制滚动行为。

创建渲染对象(Creating a render object)
EditableText创建一个称为RenderEditable渲染对象。

文本编辑在Flutter中的工作方式_第18张图片
rendereditable18.png

渲染层(Rendering layer)

RenderEditable管理命中测试、文本、和光标或者选择区域,以及从字符到字符、单词到单词的移动。最后,它使用TextPainter绘制文本和选择区域。

文本编辑在Flutter中的工作方式_第19张图片
textpainter19.png

这样就完成了和开头内容的呼应!如你所见,TextPainter创建了一个Paragraph对象,该对象将将绘制工作传给Flutter引擎。

总览

下图是上面内容的总结:

文本编辑在Flutter中的工作方式_第20张图片
textsummary20.png

你可以看看它与更大的Flutter架构是如何匹配的:

文本编辑在Flutter中的工作方式_第21张图片
flutterarchitecural21.png

最后

很高兴你能看完,假如发现任何错误,请在评论中告诉我,这样其他读者也能看到。

你可能感兴趣的:(文本编辑在Flutter中的工作方式)