一个易迁移、兼容性高的富文本方案

核心提示背景在闲鱼消息体系中,富文本在 UI 侧占了非常大的比重。最近消息部分在整体 Flutter 化,如何解决 Flutter 侧富文本问题,成为了项目早期的风险点。在 Native 中,消息使用了 HTML 协议来承载富文本的解析与展示,由于

背景

在闲鱼消息系统中,富文本在UI端占了非常大的比重。最近新闻部分变成了一个整体的颤振,如何解决颤振端的富文本问题成为项目前期的一个风险点。

在本地,消息使用HTML协议来进行富文本的解析和显示。因为报文的历史数据具有落入数据库的特性,所以我们必须在Flutter端兼容这个协议。对于Flutter,能否在兼容的基础上扩展和提升能力?

目前闲鱼也在升级Flutter 1.12,所以我们不仅需要支持当前版本的图文混排,还需要快速迁移到更高版本的系统方案。因此,我们需要找到一种兼容性高、易于移植的富文本方案。

行业地位

在业内,有一个针对旧版RichText的解决方案。详见玄川:《如何低成本实现Flutter富文本?就看这篇文章吧!》.但是这里RichText的整个环节都没有解决方案,而且Flutter自己的Rich Text也在随着版本的迭代而进化,所以我们需要一个完整的进化方案。

其实从Flutter 1.7.3开始的RichText已经解决了我们的很多烦恼。是如何实现的?它和老版本的实现有什么区别?带着疑问,我们先来分析一下它的实现原理。

图文混排的丰富原理

从Flutter 1.7.3开始,RichText不再继承LeafRenderObjectWidget,而是继承了MultiChildRenderObjectWidget。由此很容易看出,RichText将是一个布局控件,内部有多个子控件。

创作过程

如上所示,我们传递给RichText的文本参数是InlineSpan,TextSpan和WidgetSpan是它的子类。

在rich的初始化过程中,所有的小部件RichText将被递归地过滤掉,并传递给父类MultiChildRenderObjectWidget。创建MultiChildRenderObjectElement,然后RichText会通过createRenderObject生成RenderParagraph。在RenderParagraph初始化过程中会创建TextPainter,这是画图的核心。布局、绘制和事件分发将在这里执行。然后递归过滤PlaceholderSpan。渲染过程

上图显示了RenderParagraph中的performLayout函数。

如上图,RenderParagraph执行performLayout。首先_layoutChildren:用于子控件的布局,目的是获取子控件的大小,如果没有子控件则直接返回。这里说的子控件是WidgetSpan。第二步_layoutTextWithConstraints是执行_textPainter的布局方法。在这里,将构建文本,并根据其树结构执行。TextWidget build在构建时将自己的文本添加到构建器中。WidgetSpan build会将自己控件的PlaceholderDimensions添加到生成器中。这实际上是添加一个占位符,它将与控件大小相同。接下来,_paragraph会做一个布局,然后获取每个占位符的位置并存储。Paint process将首先完成带有占位符的文本的绘制,然后遍历子控件,根据步骤2-3中获得的占位符位置设置偏移量。综上所述,与旧版本相比,新版本在底层多了一个_addPlaceholder能力,用来占用混合Widget,获取位置信息。

设计理念

以HTML协议为出发点,不仅可以解决普通HTML字符串的解析和渲染,还可以用用户发送的闲鱼自定义表情符号扩展字符串的能力。下图显示了总体设计思路:

目前的消息展示分为两种场景,一种是带有闲鱼自定义emoji表情的字符串:

你好[微笑],你家宝宝很好看[露出牙齿],包含吗?[假笑][假笑]另一个是简单的HTML字符串:

“整个交易都在闲鱼。你敢买,我就敢掏钱!如果因为诈骗导致金钱和物品损失,可以获得最高5000元的赔偿。”当然还有最常见的纯文本。

对于这三种字符串,服务器不按类型区分,客户端获取所有字符串。侧面应该如何高效的处理和展示?

流程是这样设计的:

首先,对于确定为纯文本的控件,直接使用single TextSpan的RichText,省去了文本的封装。使用RegExp匹配[smile]等表情符号占位符,替换为最后一个htmlString,使用html | Dart包进行HTML解析,生成HTML节点树。递归HTML节点树文本标签映射到TextSpan图片标签映射到FDImageSpan;Flutter升级为WidgetSpan,其子项设置为Image Widget,链接标签映射为TextSpan。在定义GestureRecognizer的过程中,首先将闲鱼自定义表情符号占位符转换成HTML元素,然后统一处理HTML字符串。然后将HTML字符串转换成富文本。在设计上,有两层:数据分析层和渲染层。

如上图所示,有了前面原生的Flutter混合图文的支持,我们就可以在低配版中模仿实现了,低配版的RichText继承了LeafRenderObjectWidget。我们将RichText与其他小部件结合起来,形成一个新的MultiChildRenderObjectWidget,通过占位符正常渲染文本,然后获取占位符位置,设置相应小部件的位置。

如何在Flutter SDK升级过程中让业务端不察觉?先看下图:

发现在TextSpan树中,我们从TextSpan继承的自定义FDImageSpan实际上可以直接对应原生WidgetSpan。这里我们可以在HTML节点树映射到TextSpan树的过程中直接修改。在FDRichText构建中,我们可以直接返回到系统RichText。用户可能察觉不到这种变化。

影响

上图是最简单也是最常见的系统消息,更多使用红色字体来突出安全警告。模块中定义的三种富文本都可以定制。

上图是涉及交互的丰富文字。买家可以点击蓝色文字“那边发货”,然后买家会自动发送“那边发货”给卖家,卖家会根据预设的问题自动回复买家。点击会触发HTML字符串中的href自定义协议链接,客户端触发openURL的操作实现交互。

这是一个普通用户可以编辑和发送的富文本。丰富的闲鱼自定义表情符号穿插在文字中,既增加了聊天的乐趣,又增强了用户的表现力。

跟进计划

目前的显示部分只是图文混排,新版本中的富文本支持任何Widget,可玩性更强。所以可以扩展HTML标签的描述,这部分还需要以后继续探索。

由于篇幅有限,上面没有提到富文本编辑器。消息中的用户输入框也需要支持闲鱼自定义表情符号。该方案的当前版本直接使用占位符,并不显示实际的图片。让我们回头看看HTML协议。我们能支持新版本的TextField吗?这不仅限于自定义表情符号。

我们把注意力转向发布和婴儿的细节:

我们可能会在后续支持上述图片、发布和宝贝细节的富文本编辑和显示。对比两个详情页,可以明显看出,丰富的文字在表达上更有冲击力,买家在阅读时很容易抓住卖家想要表达的关键信息。

可见,未来在编辑和展示富文本的基础能力统一后,可以获得更多的商业利益。

 
友情链接
鄂ICP备19019357号-22