精华内容
参与话题
问答
  • Flutter中实现内嵌图像文本特征的棘手解决方案。
  • 作者:闲鱼技术-玄川 背景 闲鱼是国内最早使用Flutter 的团队,作为一个...我们面对文本类的需求是复杂而且多变,然而Flutter历史的几个版本,Text只能显示简单样式文本,它只有包含一些控制文本样式显示的属性,...

    作者:闲鱼技术-玄川

    背景

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

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

    187a3d9033acdbbb99681984804e97c4fca.jpg

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

    富文本的原理

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

    • 创建过程

    5a93331c74382af5bae7db3375b891623e2.jpg

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

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

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

      abf1fc10e6421ad90bedd8993c51d5aeb61.jpg

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

    33075887d2cc1069e16e48f03c353c70453.jpg

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

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

    通过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) 

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

    实现方案

    34961796508d1e0094860c15cf6e2d83785.jpg

    关键部分代码实现如下:

    • 统一的占位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),
             ],
           );
         }
      

    效果

    先上图看看效果

    602f7aed038df32ae73fdbad321b23a00e5.jpg

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

    未来

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


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

    转载于:https://my.oschina.net/u/1464083/blog/3081957

    展开全文
  • Soft and gentle rich text editing for Flutter applications. Zefyr is currently in early preview. If you have a feature request or found a bug, please file it at the issue tracker. > Important: Zefyr ...
  • 一款Flutter实现的富文本编辑。.zip,软和温和的富格文本编辑颤振应用程序。 基于Flutter
  • 背景闲鱼是国内最早使用和最大规模使用Flutter的团队,作为一个电商App商品详情页是非常重要场景,其中最主要的技术能力是文字混排。我们面对文本类的需求是复杂而且多变,...
        

    背景

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

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

    640?wx_fmt=jpeg

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

    富文本的原理

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

    创建过程

    640?wx_fmt=png

    创建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分别独立移动到上面。640?wx_fmt=png

    伴随这个方案有两个难点:

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

    通过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)

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

    实现方案

    640?wx_fmt=gif

    关键部分代码实现如下:

    统一的占位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: &lt;Widget&gt;[	
               RichText(),	
               Positioned(left: position.dx, top: position.dy, child: child),	
              ],	
            );	
          }

    效果

    先上图看看效果

    640?wx_fmt=jpeg

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

    未来

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

    就是现在,客户端/服务端java/架构/前端/质量工程师,小闲鱼通通期待你的加入,base杭州阿里巴巴西溪园区。欢迎天马行空的你加入我们,做有创想空间的社区产品、做深度顶级的开源项目,一起拓展技术边界成就极致!

    *投喂简历给小闲鱼guicai.gxy@alibaba-inc.com

    640?wx_fmt=png

    640?wx_fmt=png

    更多系列文章、开源项目、关键洞察、深度解读

    请认准闲鱼技术

    展开全文
  • Flutter 富文本

    2020-08-10 16:48:52
    好用的flutter富文本库。 富文本是很多App都需要的,而且Flutter也提供了富文本功能,但是对于做多语言的APP来说,RichText并不好用,或者说不能用, 今天就给大家推荐一个第三方库 rich_text_widget 使用起来简单又...

    好用的flutter富文本库。富文本是很多App都需要的,而且Flutter也提供了富文本功能,但是对于做多语言的APP来说,RichText并不好用,或者说不能用,
    今天就给大家推荐一个第三方库 rich_text_widget

    使用起来简单又方便,不用分割字符串
    具体使用方法参考如下

    RichTextWidget(
          // default Text
          Text(
            'You have pushed the button this many times:',
            style: TextStyle(color: Colors.black),
          ),
          // rich text list
          richTexts: [
            BaseRichText(
              "pushed",
              style: TextStyle(color: Colors.yellow),
              onTap: () => {print("touch pushed")},
            ),
            BaseRichText(
              "button",
              style: TextStyle(color: Colors.red),
              onTap: () => {print("touch button")},
            ),
          ],
        )
    

    展示效果如下
    demo演示

    参数说明

    RichTextWidget:

    Name type
    defaultText Text
    richTexts List <BaseRichText>

    BaseRichText:

    Name type
    data String
    style TextStyle
    onTap Function
    展开全文
  • flutter-富文本编辑框

    千次阅读 2019-08-22 13:47:55
    资源下载地址: https://download.csdn.net/download/u013425527/11592636 实现的功能: 1.写文字。 2.插入视频和图片。 3.视频的播放 4.图片的点击放大预览。 下载地址:......

    资源下载地址:

    https://download.csdn.net/download/u013425527/11592636

    实现的功能:

    1.写文字。

    2.插入视频和图片。

    3.视频的播放

    4.图片的点击放大预览。

    下载地址:https://download.csdn.net/download/u013425527/11592636

    界面展示:

        

    展开全文
  • 使用WidgetSpan能够存放不是TextSpan格式的Widget Row( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Expanded( flex: 1, child: Text.rich( TextSpan( ... W.
  • 字节跳动视频编解码面经

    万次阅读 多人点赞 2019-09-27 16:26:17
    三四月份投了字节跳动的实习(图形图像岗位),然后hr打电话过来问了一下会不会opengl,c++,shador,当时只会一点c++,其他两个都不会,也就直接被拒了。 七月初内推了字节跳动的提前批,因为内推没有具体的岗位,...
  • 大三实习生,字节跳动面经分享,已拿Offer

    万次阅读 多人点赞 2020-03-29 21:32:51
    说实话,自己的算法,我一个不会,太难了吧
  • 字节跳动面经(一、二、三+大boss+hr面)

    万次阅读 多人点赞 2019-07-18 16:15:08
    先介绍一下,本科和研究生都不是计算机专业,现在是学通信,然后做图像处理,可能面试官看我不是科班出身没有问太多计算机相关的问题,因为第一次找工作,字节的游戏专场又是最早开始的,就投递了,投递的是游戏测试...
  • 字节跳动 Flutter 架构实践

    千次阅读 2020-01-16 09:55:26
    01 移动跨平台技术趋势 移动互联网发展 10 多年来,由之前传统 PC 端时代到移动时代,在移动时代竞争激烈,各大移动互联网公司都在争相抢夺市场,如何提高研发效率,产品快速迭代、快速试错成为非常重要的因素。...
  • 字节跳动面经汇总

    千次阅读 2020-02-03 23:59:18
    字节跳动面经 【字节跳动字节跳动-抖音C++开发实习一二面凉经 https://www.nowcoder.com/discuss/342523 【字节跳动字节跳动后端面经 已拿意向书 https://www.nowcoder.com/discuss/302265 【字节跳动】字节c++...
  • 字节跳动上班有多累?

    万次阅读 多人点赞 2020-06-07 12:09:06
    字节跳动上班有多累?前言面试邀约面试过程一面的出乎意料二面的游刃有余三面的压力测试立FLAG入职字节一年 人员三年临近崩溃第一次崩溃第二次崩溃第三次崩溃最新状态 前言      大部分的人都渴望能进入字节跳动...
  • 面试字节跳动,我被怼了....

    万次阅读 多人点赞 2019-03-03 08:48:06
    人们都说,这个世界上有两种人注定单身,一种是太优秀的,另一种是太平凡的。 我一听 呀?那我这岂不是就不优秀了吗,于是毅然决然和女朋友分了手。 人们都说,互联网寒冬来了,这个时候还在大面积招人的公司,必然...
  • flutter 富文本编辑器 支持图文、视频混排。 效果预览 Getting Started 首先需要继承实现RichEditController。 简单使用可直接用下面的例子。 chewie: 0.9.10 video_player: 0.10.11 image_picker: 0.6.7 import...
  • 字节跳动,百度的 ” “威力加强版” :虽然媒体热衷于炒作“头腾大战”,字节跳动最贴切的对标其实是百度。它具备百度巅峰期的优点(技术领先、销售强势),又避免了导致百度衰落的缺点(不重视用户体验、执行力...
  • 背景在闲鱼消息体系中,富文本在 UI 侧占了非常大的比重。最近消息部分在整体 Flutter 化,如何解决 Flutter富文本问题,成为了项目早期的风险点。在 Native 中,消...
  • 如果能过字节的笔试,那应该没什么笔试能难倒你了,祝所有秋招的同学offer拿到手软 如果你从本文中学习到丝毫知识,那么请您点点关注、点赞、评论和收藏 大家好,我是爱做梦的鱼,我是东北大学大数据实验班大三的小...
  • 富文本展示控件,因版本冲突特修改后引用 https://github.com/ztj666/flutter_html_widget 1、配置文件pubspec.yaml flutter_html_widget: git: url: https://github.com/ztj666/flutter_html_widget.git ...
  • 写在前面 为什么只看这一篇就够了? 现在CSDN、知乎、掘金上各路大佬层出不穷,他们身经百战、血洗杀场,总结出满满的干货,方便刚刚入此行的道友们少走弯路,拿到心仪的offer。但同时也存在很多非良心的博主,要么...
  • 字节跳动这家公司,应该是所有秋招的公司中,对算法最重视的一个了,每次面试基本都会让你手撕算法,今天这篇文章就记录下当时被问到的几个算法题,并且每个算法题我都详细着给出了最优解,下面再现当时的面试场景。...
  • 字节跳动面试汇总

    千次阅读 多人点赞 2020-02-20 22:21:43
    字节跳动面试汇总 【字节跳动字节跳动-抖音C++开发实习一二面凉经 https://www.nowcoder.com/discuss/342523 【字节跳动字节跳动后端面经 已拿意向书 https://www.nowcoder.com/discuss/302265 【字节跳动】字节...
  • 实习生活之字节跳动

    万次阅读 多人点赞 2019-07-16 15:23:03
    博主上半年因为课程少(其实就是闲)的缘故,向各大中小 IT 公司,如字节跳动,360等的实习生岗位投递了简历,广撒网,多敛鱼,择优而从之。最终,博主看中字节跳动的高待遇——一天400,就近租房补贴,免费的一日三...
  • 字节跳动前端面试经历及总结

    万次阅读 多人点赞 2018-12-15 02:08:32
    今天我参加了字节跳动的前段视频第一次面试,把它记录总结下来,希望能够对大家带来帮助。 楔子 投递简历后过了一段时间接到约面小姐姐的电话,了解你的具体的实习时间段,和你约定面试时间。你确认后小姐姐挂电话...
  • 字节跳动(今日头条),为何战斗力如此凶猛?

    万次阅读 多人点赞 2019-02-20 11:36:11
    本文转载自公众号 中产之路年前,一位久未联系的朋友问京杭君:有没有研究过今日头条? 还有没有上升空间?这位朋友在杭州阿里工作多年,后出来创业,有猎头联系他,今日头条要在...
  • 字节跳动面试题

    千次阅读 2019-10-04 11:36:21
    字节跳动面试题 Http协议 Http协议是一个应用层的协议,由请求和响应构成,使用统一资源标识符来传递数据和建立连接 。 HTTP协议是无状态的,也就是说每一次HTTP请求之间都是相互独立的,没有联系的,服务端不...
  • 富文本 RichText( text: TextSpan( text: ‘文本内容’, 此内容作为根内容和其他字符串连在一起,也可也不写,在children中的TextSpan中写,则会变成兄弟内容和其他字符串连在一起 style: TextStyle(), 样式 ...
  • 今天正式入职了字节跳动。工号超吉利,尾数是4个6。然后办公环境也很好,这边一栋楼都是办公区域。公司内部配备各种小零食、饮料,还有免费的咖啡。15楼还有健身房。而且公司包三...
  • 字节跳动2020届秋招笔试题

    万次阅读 2019-08-12 09:05:04
    字节跳动2020届秋招第一批笔试题(2019.8.11) 文章目录字节跳动2020届秋招第一批笔试题(`2019.8.11`)第一题[编程题25分]: 闹钟叫醒去上课第二题[编程题25分]: 秘密通信第三题[编程题25分]: 万万没想到之抠门的...

空空如也

1 2 3 4 5 ... 20
收藏数 21,207
精华内容 8,482
关键字:

flutter富文本