如何低成本实现Flutter富文本,看这一篇就够了!

作者:闲鱼技术-玄川

背景

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

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

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

富文本的原理

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

  • 创建过程

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

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

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

    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分别独立移动到上面。

然而上面这种方案会带来两个难点:

  • 难点一:如何在文本中先占位,并且能制定任意想要的宽高。

通过Google 发现u200B字符代表ZERO WIDTH SPACE(宽带为0的空白),结合对TextPainter测试,我们发现layout出来的Width总是0,fontSize只决定了高度,结合TextStyle里面的letterSpacing

/// The amount of space (in logical pixels) to add between each letter
/// A negative value can be used to bring the letters closer.
final double letterSpacing;

这样我们就能任意的控制这个特殊文字的宽高度。

  • 难点二:如何将特殊的Span移动到位置上面。

通过上面的测试不难发现,特殊的Span其实还是独立Widget和RichText并不融合。所以我们需要知道当前widget相对RichText空间的相对位置,并且结合Stack将其融合。结合TextPaint里面的getOffsetForCaret方法

/// Returns the offset at which to paint the caret.////// Valid only after [layout] has been called.Offset getOffsetForCaret(TextPosition position, Rect caretPrototype) 

可以天然的获取到当前占位符相对位置。

实现方案

关键部分代码实现如下:

  • 统一的占位SpaceSpan

    SpaceSpan({this.contentWidth,this.contentHeight,this.widgetChild,GestureRecognizer recognizer,
    }) : super(style: TextStyle(color: Colors.transparent,letterSpacing: contentWidth,height: 1.0,fontSize:contentHeight),text: '\u200B',recognizer: recognizer);
  • SpaceSpan 相对位置获取

    for (TextSpan textSpan in widget.text.children) {if (textSpan is SpaceSpan) {final SpaceSpan targetSpan = textSpan;Offset offsetForCaret = painter.getOffsetForCaret(TextPosition(offset: textIndex),Rect.fromLTRB(0.0, targetSpan.contentHeight, targetSpan.contentWidth, 0.0),);........}textIndex += textSpan.toPlainText().length;}
    
  • RichtText和SpaceSpan融合

      Stack(children: <Widget>[RichText(),Positioned(left: position.dx, top: position.dy, child: child),],);}
    

效果

先上图看看效果

这种方案的优点是任意Widget可通过SpaceSpan和RichText进行组合,无论是图片、自定义标签、甚至是按钮都可以融合进来,同时对RichText本身封装性破坏较小。

未来

上面只是富文本显示的部分,依然存在着很多局限,还有较多需要优化的点,目前通过SpaceSpan 控件,必需要指定宽高,另外对于文本选择、自定义文字背景这些都是无法支持,其次对富文本编辑器的支持,可以使其编辑文字时,让图片、货币格式化等控件输入等。


原文链接
本文为云栖社区原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/518261.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

赋能零售成长型企业营销增长,云徙「数盈·新营销中台」发布

巨石崩裂时&#xff0c;有人看见了恐惧&#xff0c;有人看见了光。 因为有光&#xff0c;万物生长。 疫情给每个企业的影响都是巨大的。但在疫情冲击之下&#xff0c;由中台技术推动企业数字化转型&#xff0c;又给企业带来了希望。 从2016年创业至今&#xff0c;云徙科技的每…

TortoiseGit 下载、安装、配置_入门试炼_01

TortoiseGit 简介: TortoiseGit 简称 tgit&#xff0c; 中文名海龟Git。TortoiseGit是一个开放的GIT版本控制系统的源客户端。 文章目录一、软件下载1.1. 下载安装Git1.2. Tortoisegit二、安装流程2.1. 双击安装程序2.2. 直接点击下一步(Next)2.3. Next2.4. 选择安装目录2.5. 点…

ArchSummit分享 | 高德地图App架构演化与实践

讲师介绍 郝仁杰&#xff0c;高德地图无线开发专家。在7月13日落幕的2019年ArchSummit峰会上就高德地图近几年的App架构演化和实践进行了分享。 背景概述 高德是国内领先的数字地图内容、导航和位置服务解决方案提供商&#xff0c;端上分手机和车机两条主线。近年来&#xf…

工程师的灵魂拷问:你的密钥安全吗?

阿里妹导读&#xff1a;密钥管理是密码学应用的核心问题之一。任何涉及加密/签名的应用&#xff0c;无论算法本身机制多么安全&#xff0c;最终都会受到灵魂拷问&#xff1a;你密钥存在哪儿&#xff1f;本文实现了一种安全的密钥管理方案&#xff0c;基于安全多方计算技术&…

优化算法2D可视化的补充

4. 分析上图&#xff0c;说明原理&#xff08;选做&#xff09; 1、为什么SGD会走“之字形”&#xff1f;其它算法为什么会比较平滑&#xff1f; 之所以会走"之字形"&#xff0c;是因为它在每次更新参数时只考虑当前的样本梯度。这导致参数更新非常不稳定&#xff0c…

TortoiseGit 本地仓库和远程仓库建立联系_入门试炼_02

TortoiseGit 简介: TortoiseGit 简称 tgit&#xff0c; 中文名海龟Git。TortoiseGit是一个开放的GIT版本控制系统的源客户端。 文章目录一、前提准备1.1. 生成秘钥和公钥1.2. 把 SSH Key 填到Git 服务器的配置中一、前提准备 声明&#xff1a;此处介绍通过SSH URL方式传输&…

来自 Spring Cloud 官方的消息,Spring Cloud Alibaba 即将毕业

2019 年 7 月 24 日晚&#xff0c;Spring Cloud 官方发布公告&#xff1a; 仓库迁移是官方决定Spring Cloud Alibaba 即将毕业 根据官方最新的发版规则&#xff0c;我们会把孵化器中的 Spring Cloud Alibaba 仓库迁移回 Alibaba 官方仓库&#xff0c;进行正式的毕业发布&#…

TortoiseGit 克隆_入门试炼_03

文章目录一、 Git克隆1.1. 右击选择Git克隆1.2. 输入URL1.3. 提示输入密码一、 Git克隆 1.1. 右击选择Git克隆 在本地文件夹的空白位置处&#xff0c;右击鼠标&#xff0c;在菜单中选择【Git克隆】&#xff1a; 1.2. 输入URL 标签说明URL是git项目地址&#xff0c;简言之后…

5分钟在PAI算法市场发布自定义算法

概述 在人工智能领域存在这样的现象&#xff0c;很多用户有人工智能的需求&#xff0c;但是没有相关的技术能力。另外有一些人工智能专家空有一身武艺&#xff0c;但是找不到需求方。这意味着在需求和技术之间需要一种连接作为纽带。 今天PAI正式对外发布了“AI市场”以及“PA…

Gartner:阿里云蝉联全球第三、亚太第一

4月23日消息&#xff0c;国际研究机构Gartner发布最新云计算市场追踪数据&#xff0c;阿里云亚太市场排名第一&#xff0c;全球市场排名第三。阿里云亚太市场份额从26%上涨至28%&#xff0c;接近亚马逊和微软总和&#xff1b;全球市场份额从7.7%上涨至9.1%&#xff0c;进一步拉…

消息点击率翻倍,原来这就是闲鱼背后的神器

阿里妹导读&#xff1a;IFTTT是一个被称为 “网络自动化神器” 的创新型互联网服务理念&#xff0c;它既实用&#xff0c;概念又简单&#xff0c;可以通过标准化协议满足用户的强需求&#xff0c;让各种互联网产品为用户服务&#xff0c;2010年刚推出&#xff0c;就拥有了极高的…

TortoiseGit 将工作区变动文件提交本地仓库_入门试炼_04

文章目录一、将工作区代码提交到本地仓库1.1. 新增/改动文件1.2. 将变动文件提交本地仓库1.3. 填写提交注释说明一、将工作区代码提交到本地仓库 Git的使用类似TFS、SVN等源代码或者文件管理器&#xff0c;惯例的流程&#xff1a; 1.1. 新增/改动文件 改动/修改本地项目中的…

MongoDB Sharding 请勿复用已删除的 namespace

SERVER-17397: Dropping a Database or Collection in a Sharded Cluster may not fully succeed 是 MongoDB 里老大难的问题&#xff0c;库或集合删除操作如果没有完全执行成功&#xff0c;再新建相同名字的集合&#xff0c;可能导致读到老版本数据的问题。 集合分片原理 Mon…

32岁被裁补偿N+2:“感谢裁我,让我翻倍!” 网友:求同款被裁!

2020年的冬天&#xff0c;“冷”的有些频繁。最近浏览一则新闻&#xff0c;19年12月19日&#xff0c;《马蜂窝被曝裁员40% UGC模式变现难&#xff1f;》爆火&#xff0c;据悉马蜂窝将裁员40%&#xff0c;交易中心成了“重灾区”&#xff0c;赔偿N2&#xff0c;留下的除搜索推荐…

JavaScript-操作DOM对象-创建和插入dom节点

插入节点 我们获得了某个Dom节点&#xff0c;假设这个dom节点是空的&#xff0c;我们通过innerHTML就可以增加一个元素了&#xff0c;但是这个DOM节点已经存在元素了&#xff0c;我们就不能这么干了&#xff01;会产生覆盖 将标签 追加 到其他标签 <p id"js">J…

技术架构演进|0到千万DAU,微淘如何走过?

导读&#xff1a;大家经常看到手淘里面的第二个TAB 就是微淘了&#xff01;目前有几千万 DAU&#xff0c;几百亿关注关系&#xff0c;每天几十万的商家生产内容&#xff0c;对系统的挑战较大。产品形态上目前以关注 feeds 流为主&#xff0c;是商家非常重要的获取流量阵地&…

TortoiseGit 更新远程仓库最新代码到本地仓库_入门试炼_05

文章目录1. 更新项目1. 更新项目 拉取和远程仓库保持版本一致

自动驾驶中高精地图的大规模生产:视觉惯导技术在高德的应用

导读&#xff1a;导航、驾驶辅助、自动驾驶等技术的不断发展对地图的精细程度提出了更高的要求。常规的道路级地图对于智能交通系统存在很多不足&#xff0c;针对自动驾驶应用的需求&#xff0c;我们提出了利用视觉惯导技术制作高精地图的方法。 本文将首先介绍视觉和惯导的主…

如何选择基于 Kubernetes 的 PaaS?

作者 | Bram Dingelstad译者 | 弯月&#xff0c;责编 | 郭芮头图 | CSDN 下载自视觉中国出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;我们都遇到过这种情况&#xff1a;有人发现了一个bug&#xff0c;然而这不是一般的软件bug&#xff0c;甚至都不是通常意义上的…

TortoiseGit 推送本地仓库变动文件至远程仓库_入门试炼_06

文章目录1. 将本地仓库变动文件提交远程1. 将本地仓库变动文件提交远程 或者