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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: flutter富文本演进

作者:闲鱼技术-新宿

背景

在闲鱼消息体系中,富文本在 UI 侧占了非常大的比重。最近消息部分在整体 Flutter 化,如何解决 Flutter 侧富文本问题,成为了项目早期的风险点。

在 Native 中,消息使用了 HTML 协议来承载富文本的解析与展示,由于消息的历史数据有落库的特性,我们必须在 Flutter 侧兼容这种协议。对于 Flutter,我们是否可以在兼容的基础上,进行能力的扩充与完善?

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

行业现状

行业内,对于旧版的 RichText (Flutter 1.7.3 之前)已有了解决方案,详见玄川:《如何低成本实现Flutter 富文本,看这一篇就够了!》。但这里并没有对富文本的整个链路的解决思路,且 Flutter 自身的 RichText 也在随着版本迭代进行演进,我们需要一套完整的演进方案。

事实上,Flutter 1.7.3 开始的 RichText 解决了我们的很多麻烦,它是怎么实现的呢?和旧版的实现有什么区别呢?带着问题,我们先来分析它的实现原理。

RichText 图文混排原理

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

创建过程:

richText

如上图,我们传给 RichText 的 text 参数为 InlineSpan,TextSpan、WidgetSpan都是其子类。

  1. RichText 初始化过程中会将 text 中的所有 WidgetSpan 递归筛选出来,传递给父类 MultiChildRenderObjectWidget。
  2. 创建 MultiChildRenderObjectElement,接着 RichText 会通过 createRenderObject,生成 RenderParagraph。
  3. RenderParagraph 初始化过程中会创建 TextPainter,这个是绘制的核心,这里将会进行 layout、paint 和事件分发操作;然后递归筛选 PlaceholderSpan (其实还是 WidgetSpan)。
渲染过程:

carbon

上图为 RenderParagraph 内的 performLayout 函数。

  1. 如上图 RenderParagraph 执行 performLayout 。首先 _layoutChildren:为子控件布局,目的为获取子控件大小,如果没有子控件则直接 return。这里所说的子控件就是 WidgetSpan。
  2. 第二步 _layoutTextWithConstraints,就是执行 _textPainter 的 layout 方法,这里会让 text(InlineSpan)进行 build,此时会按照它的树形结构遍历执行。

    1. TextWidget build 时会将自身的 text(这是真实的字符串)addText 给 builder。
    2. WidgetSpan build 时会将自身控件的 PlaceholderDimensions 信息,addPlaceholder 给 builder。这里其实就是添加占位符,占位符将与控件同大小。
    3. 紧接着 _paragraph 会进行一次布局,然后获取各个占位符位置存储下来。
  3. Paint 过程会先将带着占位符的文本绘制完成,然后遍历子控件按照 2-3 步骤中获得的占位符位置,设置偏移。

概括来说,新版本对比旧版本,底层多了个 _addPlaceholder 能力,用来占位混排的 Widget,并获取位置信息。

设计思路

我们以 HTML 协议为抓手,不光可以解决普通 HTML 字符串的解析与渲染,也可以对用户发送的带闲鱼自定义 emoji 的字符串进行能力的扩充。下图为大致的设计思路:

richText2

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

你好[微笑],你的宝贝不错哦[呲牙],包邮吗?[坏笑][坏笑]

另一种为简单的 HTML 字符串:

"<font color="#888888">交易全程在闲鱼,</font><strong><font color="#F54444">你敢买,我敢赔!</font></strong><font color="#888888">若遇欺诈造成</font><strong><font color="#F54444">钱货两失,可获赔</font></strong><strong><font color="#F54444">最高5000元</font></strong>"

当然,还有最普通的纯文本。

对于这三种字符串,服务端并没有用类型来给我们区分,客户端拿到的都为字符串。端侧该如何处理且高效展示呢?

过程设计成这样:

  1. 首先对于确定为纯文字的控件,直接使用单 TextSpan 的 RichText,免去 Text 的封装。
  2. 使用 RegExp(r'\[[^\]\[]+\]') 匹配 [微笑] 等 emoji 占位符,替换为 <img src=003_微笑.png width=22.400000 height=22.400000/>
  3. 取最后的 HTMLString ,使用 [html | Dart Package ],进行 HTML 解析,生成 HTML Node Tree
  4. 递归 HTML Node Tree

    1. 文本标签映射为 TextSpan
    2. 图片标签映射为 FDImageSpan;Flutter 升级后将其替换为 WidgetSpan,其 child 设置为 Image Widget
    3. 链接标签映射为 TextSpan,定义 GestureRecognizer 相应手势

流程上,先将闲鱼自定义 emoji 占位符转为 HTML 元素,接着统一处理 HTML 字符串。然后将 HTML 字符串统一转为富文本。设计上,分为两层:数据解析层、渲染层。

richText1

如上图,有了前面原生 Flutter 图文混排支撑,我们在低版本可以仿照实现,低版本 RichText 继承自 LeafRenderObjectWidget,我们把 RichText 与其他 Widget 组成新的 MultiChildRenderObjectWidget,通过占位符正常渲染文本,之后获取占位符位置,设置对应 Widget 的位置。

Flutter SDK 升级过程中如何保持业务方无感知?先看下图:

richText0

对比发现,在 TextSpan 树中,我们继承自 TextSpan 的自定义 FDImageSpan,实际上可以直接对应到原生的 WidgetSpan,这里我们可以在 HTML Node Tree 映射到 TextSpan Tree 的过程中直接修改。而 FDRichText build 里,我们可以直接返回系统 RichText。这样的改动,对于使用方可以做到无感知。

效果

上图中是一种最为简单和常见的系统消息,为了突出安全警示,使用了较多的红色字体。模块中定义的三个富文本,均可定制样式。

效果2

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

效果3

这是普通用户可以编辑发送的富文本,丰富的闲鱼自定义 emoji,穿插在文字中,不仅增加了聊天乐趣,也增强了用户的表达。

未来

当前的展示部分仅仅是图文混排,新版本中的富文本支持任意 Widget,可玩性更高,所以我们对 HTML 标签描述可以进行扩充,这部分未来还需要持续探索。

由于篇幅有限,上文并没有讲述富文本编辑器。消息中用户输入框也需支持闲鱼自定义 emoji,当前版本的方案为直接使用占位符(比如“[微笑]”),并不展示实际的图片。我们回头再看一下 HTML 协议,对于新版的 TextField,我们可以支持吗?这就不仅仅局限在自定义 emoji 里了。

我们把目光转向发布和宝贝详情:

展望富文本

我们后续可能会支持上图中,发布和宝贝详情的富文本编辑和展示。对比两个详情页,很明显能看出,使用富文本的方式,在表达上更加富有冲击力,买家阅读起来能很容易抓住卖家想表达的关键信息。

可见,未来在富文本的编辑、展示基础能力统一之后,可以让更多业务收益。

相关文章
|
6月前
|
存储 JavaScript 前端开发
盘点主流 Flutter 状态管理库2024
状态管理是每个应用不可缺少的,本文将会盘点下主流的状态管理包。
340 2
盘点主流 Flutter 状态管理库2024
|
XML JSON 前端开发
官方Lottie库能力增强实现
背景Lottie提供了播放复杂、酷炫动能力画,在移动端被广泛利用。在我们的应用中也被频繁、大量使用。它使用简单,仅需几行代码就能播放设计师设计的动画,帮助开发节省了时间成本。也正因为使用频繁,在使用过程中我们遇到了一些相关的问题。使用Lottie支持加载本地文件播放,也支持远程下载zip,json文件进行播放。这俩者在我们平时开发中都有使用到。本地播放本地播放比较简单。可以直接在xml实现,也可以
1183 0
官方Lottie库能力增强实现
|
15天前
|
Dart UED 开发者
flutter鸿蒙版本通过底部导航栏的实现熟悉架构及语法
这篇博客详细解析了一个 Flutter 应用的完整代码,实现了带有底部导航栏的功能,允许用户在不同页面之间切换。通过逐行讲解,帮助读者理解 Flutter 的结构、状态管理和组件交互。代码涵盖了从引入包、创建主入口、定义无状态和有状态组件,到构建用户界面的全过程。希望对 Flutter 开发者有所帮助。
144 3
|
4月前
|
JavaScript 小程序 容器
uniapp 开发规范(兼容性适配)
uniapp 开发规范(兼容性适配)
144 1
|
移动开发 缓存 Dart
Pad大屏&Flutter多引擎适配之路
# 背景 在电商场景中iPad的大屏拥有比普通手机相比更大的屏幕,对于购物体验而言,如能充分利用好iPad的大屏体验,无疑提高用户购买体验,但一直以来在混合栈应用特别是Flutter混合栈中,在iPad大屏适配和Flutter多引擎适配都是个老大难问题。本文会介绍闲鱼在这iPad适配中的各个疑难点。 # 分屏模式 华为,oppo等厂商折叠屏的方案。界面会在展开和折叠时展示不同的视图样式。op
Pad大屏&Flutter多引擎适配之路
|
存储 Dart 前端开发
原来Flutter代码是这样运行在原生系统的!快来了解Flutter标准模板,感受原生系统中Flutter的魅力!
原来Flutter代码是这样运行在原生系统的!快来了解Flutter标准模板,感受原生系统中Flutter的魅力!
86 0
|
JSON Android开发 数据格式
Flutter从0到1实现高性能、多功能的富文本编辑器(模块分析篇)
经过前面两篇文章的基础知识铺垫,我们终于要进入到专栏的核心内容 — 富文本。富文本编辑器可以说是APP中最复杂,但使用场景又极广的组件之一。例如各大笔记APP的核心功能、闲鱼的商品发布功能、还
Flutter从0到1实现高性能、多功能的富文本编辑器(模块分析篇)
|
开发工具 iOS开发 git
【iOS】代码片段汇总与迁移管理
【iOS】代码片段汇总与迁移管理
104 0
【iOS】代码片段汇总与迁移管理
|
存储 JavaScript 前端开发
北海(Kraken)构建大前端混合渲染技术体系 —— Web 与 Flutter Widget 混合渲染方案
北海(Kraken)构建大前端混合渲染技术体系 —— Web 与 Flutter Widget 混合渲染方案
474 1
北海(Kraken)构建大前端混合渲染技术体系 ——  Web 与 Flutter Widget  混合渲染方案
|
iOS开发 Perl
flutter和iOS原生混合开发流程
熟悉flutter开发的人都知道,flutter和移动端原生混合开发的方式有两种: 一种是以flutter项目为主,使用plugin插件的方式将原生部分集成到flutter项目中; 另一种则是把flutter部分作为一个模块嵌入到原生的项目中。 作为一名iOSer,今天来介绍一下flutter和iOS原生项目混合开发的流程。
flutter和iOS原生混合开发流程