怎么样用低成本实现Flutter富文本?闲鱼技术团队告诉你
署名2021-01-05

闲鱼是国内最早使用和最大规模使用Flutter的团队,作为一个电商App商品详情页是非常重要场景,其中最主要的技术能力是文字混排。

我们面对文本类的需求是复杂而且多变,然而Flutter历史的几个版本,Text只能显示简单样式文本,它只有包含一些控制文本样式显示的属性,而通过TextSpan连接实现的RichText也只能显示多种文本样式,这些远远达不到设计需要的能力。被产品和设计怂为啥别人别的平台能做,Flutter为何做不了,不管,必须支持!

因此,需要开发一个能力更强的文字混排组件就变得迫在眉睫!

富文本的原理

在讲文字混批组件设计实现前,先来讲讲系统RichText的富文本的原理。

创建过程


创建RichText节点的时候其实会创建以下几个对象:

  1. 先创建LeafRenderObjectElement实例。
  2. ComponentElement方法当中会调用RichText实例的CreateRenderObject方法,生成RenderParagraph 实例。
  3. RenderParagraph 会创建TextPainter 负责其就计算宽高和绘制文本到Canvas 的代理类,同时TextPainter 持有TextSpan 文本结构。

RenderParagraph实例最后会将自身登记到渲染模块的Dirty Nodes当中去,渲染模块会遍历Dirty Nodes 将进入RenderParagraph 渲染环节。

渲染过程

RenderParagraph 方法当中封装的是将文本绘制到 canvas 上面的逻辑,主要是用了一个叫做 TextPainter 的模块,其调用过程遵循RenderObject 调用。

  1. PerfromLayout 过程通过调用TextPaint的Layout,在期过程中通过TextSpan 结构树,依次通过AddText 添加各个阶段的文本,最后通过Paragraph的Layout 计算文本高度。
  2. Paint 过程,先绘制clipRect,接着通过TextPaint的Paint函数调用,Paragraph的Paint绘制文本,最后绘制drawRect。

设计思路

通过RichText的文本绘制原理,我们不难发现TextSpan记录了各段文本信息,TextPaint通过记录的信息调用Native接口计算宽高,以及将文本绘制到canvas上面。传统的方案实现复杂的混排,会通过HTML去做一个WebView的富文本,使用WebView在性能上自然不及原生实现,出于性能的考虑,我们设想通过通过原生的方式去实现图文混排。一开始的方案是设计几种特殊的Span(例如:ImageSpan,EmojiSpan等),通过Span记录的信息,在TextPaint的Layout 重新根据各种类型重新计算布局,在Paint过程再分别绘制特殊的Widget,然而这种方案对上面几个涉及的类封装破坏的特别大,需要将RichText、RenderParagraph 源码Copy 出来重新修改。最后设想是后可以通过特殊的文字先占位置,(例如:空字符串),然后在这个文字的位置上面把特殊的Span分别独立移动到上面。