Flutter如何将文本与图片混合编辑?(功能扩展篇)

前言

一个优秀的富文本,应该包含优秀的排版算法、丰富的功能和渲染的高性能。在上一篇中,我们实现了可扩展的、基础的富文本编辑器。那么在本文中,让我们对富文本进行更多功能的扩展。

注:

— 为了在阅读本文时有更好的体验,请先阅读本专栏第一篇,前文涉及到的知识点,本文不再赘述。(摸鱼的朋友请忽略)

— 完整代码太多, 文章只分析核心代码,需要源码请到 代码仓库

文本与图片混排

在有关富文本的业务需求中,或其他文章渲染中,图文混排的功能是十分重要的。在Flutter中,为了解决这个图文混排的问题,有一个很方便的组件:WidgetSpan。而在本专栏的第一篇的文本基础知识中,已经分析了TextSpan在文本渲染过程中的作用。那么WidgetSpan是如何被渲染的呢,Flutter又是如何将TextSpanWidgetSpan混合渲染在一起的呢?

—— 效果图完整代码在仓库demo/image_text

因为Flutter提供了WidgetSpan,所以效果图中的布局十分简单:

在之前的文章中,我们已经知道RichText实际上是需要一个InlineSpan,而TextSpanWidgetSpan(中间还有个PlaceholderSpan)都是InlineSpan的子类实现。RichText最后会将InlineSpan传入RenderParagraph中。那么这个InlineSpan是一个什么样的呢?

InlineSpan树的结构

现在将目光先移到Text()Text.rich()的构造函数上,我们可以看到,在Text()组件中,它的构造函数只有一个必要参数:data,且textSpan = null,而在Text.rich()的构造函数中,也只有一个必要参数:textSpan

然后将目光移到build上,在其主要逻辑中,我们可以发现,RichText在构造时传入的text是一个TextSpan,当采用data作为必要参数传入时,text参数才会有值,当采用textSpan作为参数传入时,children才不会为null。

经过上面的分析之后,我们可以将树的结构总结为两张图:

  • 当采用data作为必要参数传入时,树中只会存在一个根节点

  • 当采用textSpan作为参数传入时,树中会存在多个子树

树中的每一个TextSpan都包含text和style,其中的style是文本样式,如果没有设置某一个节点的样式,那么它会继承父节点中的样式。若根节点也没有自定义样式,那么就会采用默认的样式值。

WidgetSpan混入InlineSpan树结构

将目光移到RichTextcreateRenderObject方法上,可以看到RichText创建的渲染对象为RenderParagraph,并且将InlineSpan传入。

再将目光移到RenderParagraphperformLayout函数上,它是RenderParagraph的重要逻辑,用于计算RenderParagraph的尺寸和child的绘制位置。

但是,这里计算的child不是TextSpan,而是PlaceholderSpan。通过_extractPlaceholderSpans挑选出所有的PlaceholderSpanvisitChildrenInlineSpan中的方法,通过该方法能遍历InlineSpan树。

到这里,对于InlineSpan树的结构已经清晰了,在树中,除了TextSpan,还存在着PlaceholderSpan类型的节点,而WidgetSpan又是继承于PlaceholderSpan的。

不过,PlaceholderSpan只是一个占位节点,RenderParagraph并不会对其进行绘制,RenderParagraph只负责确定它的大小和需要绘制的位置。RenderParagraph只需在布局的时候,将这个绘制的区域预留给WidgetSpan,这样绘制时就不会改变树的结构。

计算WidgetSpan的绘制区域

performLayoutRenderParagraph的布局函数,performLayout内部主要调用了三个函数:

  • _layoutChildren函数主要是用于计算确认PlaceholderSpan占位节点的大小。

  • _setParentData此函数用于将父节点的设置给子节点,具体的计算(尺寸计算、偏移计算)都在_layoutTextWithConstraints函数中完成。

  • _layoutTextWithConstraints此函数包含主要的布局逻辑。其中的_textPainterRichTexttext传入RenderParagraph时,RenderParagraphtext保存在_textPainter中。setPlaceholderDimensions方法用于设置InlineSpan树中每个占位符的尺寸。

    setPlaceholderDimensions将各占位节点尺寸设置完成之后,会调用_layoutText来进行 布局。

    调用layout方法,就代表着进入了TextPainter,开始创建ParagraphBuilder,然后进入引擎层开始绘制。

到这里,我们已经了解了图文混排中的图,是如何被混入文本一起渲染的了。下面让我们开始探索,如何将文本与图片放在一起编辑。

文本与图片混合编辑

要想将文本与图片混合编辑,就要在构建InlineSpan树时,在Image()外嵌套一层WidgetSpan,并将其混入InlineSpan树。而其中较为复杂的是对TextRange的位置改变的计算(添加图片、删除图片)。接下让我们一起探索,文本与图片混合编辑的秘密。

输入为图像时的Style处理

若用户操作为插入图片,则该操作不存入Style,若为文本的插入,根据TextRange,判断所需要的Style

构建InlineSpan树
  • 定义行为添加函数,将用户行为通过该函数保存。

  • 将用户行为映射到生成的InlineSpan

  • 构建InlineSpan树

通过image_picker插件,实现插入图片

尾述

在这篇文章中,我们实现了将文本与图片混合编辑的功能,其他需要插入的模块也能举一反三实现,例如插入视频。本专栏实现的富文本编辑器对于真实的复杂需求也只是一个小玩意,也有着较多的缺陷,依靠我一个人的力量也是很难实现标题中说的《高性能、多功能的富文本编辑器》,本专栏旨在于引领大家走入Flutter富文本编辑器的世界,而不单单只是学会使用已有的插件,却不了解其中的实现原理,当然这是一个超级大坑🤣。例如文本与图片的排版问题...这些缺陷都需要很多的时间一点点处理解决,也希望在将来能有更多的朋友与我一起探索文本的世界。而在后续的系列文章中,将会把富文本更加的完善,完成一个笔记的Demo,也会有对富文本性能的优化与分析。希望这篇文章能对你有所帮助,有问题欢迎在评论区留言讨论~

参考

flutter_quill

zefyrka

参考链接


Flutter如何将文本与图片混合编辑?(功能扩展篇)

发布者

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注