首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >真•文本环绕问题的探究和分享

真•文本环绕问题的探究和分享

作者头像
用户1907613
发布于 2023-10-25 02:22:04
发布于 2023-10-25 02:22:04
35300
代码可运行
举报
文章被收录于专栏:Android群英传Android群英传
运行总次数:0
代码可运行

前言

上周领导安排了一个任务:希望我们的动态展示不是固定把图片展示在文本的上面或者下面,希望图片放在文本内容里,也不需要很复杂的效果,就排版好看就行。

Ok,这不就是富文本吗,我一下子就联想到了RichText,一想到RichText支持WidgetSpan,我就知道问题不大,但是经过测试发现这里面是个大坑......

话不多说,先展示一下本地Demo的实际效果图:


--- 本文编辑于:Flutter - 真•文本环绕问题的探究和分享

正文开始

示例一 : 解释Inline的行为

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

dart
class _HomeState extends State<Home> {
  late TextSpan textSpan;
  @override
  void initState() {
    super.initState();
    textSpan = TextSpan(
        style: const TextStyle(color: Color.fromARGB(221, 99, 72, 85)),
        children: [
          TextSpan(text: tianlong[0],style: const TextStyle(color: Color.fromARGB(192, 87, 96, 230),fontSize: 22)),
          TextSpan(text: tianlong[1]),
          WidgetSpan(
              child: Image.network(
            "https://static.wikia.nocookie.net/spongebobsquarepants/images/a/ad/Spongebob-squarepants-1-.png/revision/latest?cb=20200215024852&path-prefix=zh",
            width: 100,
            height: 100,
          )),
          TextSpan(text: tianlong[2]),
          TextSpan(text: tianlong[3]),
          TextSpan(text: tianlong[4]),
        ]);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: SingleChildScrollView(child: RichText(text: textSpan)),
    );
  }
}

看文本中包含图片的一行的高度等于图片的高度,这就是InlineSpan固有的行为。不管TextSpan还是WidgetSpan都是继承自InlieSpan,也就是说不论你的WidgetSpan包含什么样的child,在布局WidgetSpan的时候,它所在行的行高一定会取那一行中最高的作为行高,所以很显然自带的RichText不作处理无法直接展示文字环绕效果。

探讨文本是如何渲染的:

看一下RichText和其对应的RenderObject的关系:

当我们把TextSpan交给RichText之后,其实所有的布局、绘制都是交由对应的RenderObject:RenderParagraph来完成的

RenderParagraph的构造函数:

在构造函数中又将textSpan交给了TextPainter类

RenderParagraph布局主要过程如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制


-   1:对所有占位元素进行布局,计算占位元素尺寸等信息
-   2:对文本进行布局
-   3:设置占位元素的位置
分析1:

由于RenderParagraph混入了ContainerRenderObjectMixin,所以将对其包含的所有children(行内元素)进行布局,那么RenderParagraph的children从哪里来的:

其实在RichText的构造函数中已经传给super,交由MultiChildRenderObjectWidget持有,而MultiChildRenderObjectWidget会生成对应的MultiChildRenderObjectElement,而在MultiChildRenderObjectElement附着在element树的时候会生成其对应的RenderObject的所有子Renderobject,而这些子RenderObject就是步骤一里进行布局的children

我们知道一开始传入的数据只有一个TextSpan,那么怎么就出来了children这个东西呢?来看上图中5735行的方法:

其实就是把TextSpan下所有的WidgetSpan全部收集起来了

再回到1处调用,可看到其实代码1的作用就是计算所有的WidgetSpan的布局信息的

分析2:

将刚才计算的占位元素(WidgetSpan)的布局信息设置给_textPainter,然后调用:

当对_textPainter调用layout方法之后即可计算出整个textSpan占用的尺寸等信息,其实TextPainter中还要生成对应的ui.Paragraph对象,由它来与引擎交互真正进行文本信息计算,flutter又引进了_NativeParagraph类,总之这一层是与引擎交换信息。

分析3及其后:

3其实没什么说的,就是布局偏移信息

performLayout之后的代码就是处理文本溢出等策略

本次尝试涉及到TextPainter中的能力:

注:说实在的TextPainter提供的能力实在是少的可怜,即使是ui.Paragraph也没有多出几个有用的API,只能在有限的API中尝试找到可用的方法,如果后期flutter开放更多能力自定义文本将会更加简单

getPositionForOffset:

该函数通过传入一个位置偏移量来计算出距离该位置处最近的文本偏移量

getBoxesForSelection:

该函数通过传入一个文本区域计算出这个区域中的布局方格,通常情况下每行一个方格,不过在遇到双向文本特殊情况会在一行计算出多个布局方格,具体自行测试

文本环绕的思路:

  1. 最佳方案当然是期待引擎能够提供UI.Paragraph添加可环绕的占位信息的Api,而不是当前只可添加Inline占位信息的Api
  2. 将占位区域(可环绕区域,下称定位块)看做障碍物,然后逐行进行绘制,遇到障碍物就拆分内容,跨过障碍物继续绘制,如下:
  1. 将除了障碍物的部分分割成多个矩形区域,如何分割将是一个巨大的挑战,我们的示例中将展示这种一个定位块最简单的分割方式:

上面是只有一个定位块的情况会简单很多,假如有两个定位块:

或者这样:

有很多中情况,多个块呢:

中间将会有多个缝隙,究竟要不要填充,都要计算,以及多个块之间相互交集与否,总之块越多分割起来越复杂,由于这个原因,以及后文中会提到的待完善功能,我将给出一个定位块的示例。

最难点:文本分割

正如我们所知道的,RichText接收的数据为一个单个TextSpan,且这个TextSpan会有N层嵌套,它不是一个简单文本字符串,如何来计算这个TextSpan该从哪里分割是困扰我最大的问题

TextSpan结构分析

假设一个嵌套的TextSpan如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

Dart
final t = TextSpan(
      text: "1",
      children: [
        TextSpan(text: "2", children: [
          TextSpan(text: "3"),
        ]),
        TextSpan(text: "4", children: [
          TextSpan(text: "5", children: [
            TextSpan(text: "6"),
            TextSpan(text: "7"),
          ]),
          TextSpan(text: "8"),
        ]),
        TextSpan(text: "9"),
      ],
      style: TextStyle(color: Colors.black)
    );

它是一个树状结构,对应图如下:

渲染结果如下:

所以我们看出TextSpan是按照深度优先策略进行渲染的,这样的结构可以压平成这样:

这和上面的树状图按照深度优先策略查找顺序是一样的,唯一需要处理的可能就是style的继承,压缩思路,其实就是深度遍历,然后一个个收集起来进行组合创建新的TextSpan,记住这一点,后文给出压平代码;

深Copy TextSpan:

由于TextSpan是不可变的且嵌套下去的,我们先定义一个辅助方法,根据现有的TextSpan深度Copy复制一个新的TextSpan的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

Dart
  static InlineSpan createSpanFrom(
    InlineSpan inlineSpan, {
    bool removeChildren = false,
    String? replaceText,
    TextStyle? parentTextStyle,
  }) {
    final effectStyle =
        parentTextStyle?.merge(inlineSpan.style) ?? inlineSpan.style;
    if (inlineSpan is WidgetSpan) {
      return inlineSpan;
    } else if (inlineSpan is TextSpan) {
      String? effectText =
          replaceText == "" ? null : replaceText ?? inlineSpan.text;
      return TextSpan(
        text: effectText,
        children: removeChildren
            ? null
            : inlineSpan.children
                ?.map(
                    (e) => createSpanFrom(e, parentTextStyle: inlineSpan.style))
                .toList(),
        locale: inlineSpan.locale,
        mouseCursor: inlineSpan.mouseCursor,
        onEnter: inlineSpan.onEnter,
        onExit: inlineSpan.onExit,
        recognizer: inlineSpan.recognizer,
        semanticsLabel: inlineSpan.semanticsLabel,
        spellOut: inlineSpan.spellOut,
        style: effectStyle,
      );
    }
    throw ErrorDescription("有除了TextSpan和WidgetSpan以外的Span,需要额外处理");
  }
}

TextPosition对象解析

包含两个属性int offset 和TextAffinity affinity

offset:

文本字符串中的位置,指的是对应索引字符串之后的位置

affinity:

辅助定位,主要为了应对双向文本或者强制换行的时候光标应该在哪个位置

根据TextPosition找到指定的分割位置:

通过遍历TextSpan,累积增加文本长度直到查找到TextPosition的offset恰好落在该TextSpan的范围内,将遍历过程中这个位置之前的内容合并创建出一个前导TextSpan,剩余部分合并创建一个后置TextSpan,这样就可以完成TextSpan分割了:

关键代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

dart
  void _splitSpanInPosition(TextSpan textSpan, TextPosition position) {
    final Accumulator offset = Accumulator();
    InlineSpan? result;
    textSpan.visitChildren((InlineSpan span) {
      if (result != null) {
        _trailingTextSpans.add(createSpanFrom(span, removeChildren: true));
        return true;
      } else {
        result = _doSplitSpanInPosition(span, position, offset);
        if (result == null) {
          _leadingTextSpans.add(createSpanFrom(span, removeChildren: true));
        }
      }
      return true;
    });
  }

  InlineSpan? _doSplitSpanInPosition(
    InlineSpan textSpan,
    TextPosition position,
    Accumulator offset,
  ) {
    InlineSpan? beforeCurrentTextSpan;
    InlineSpan? afterCurrentTextSpan;

    final TextAffinity affinity = position.affinity;
    final int targetOffset = position.offset;
    int endOffset = 0;
    /// 暂只支持TextSpan
    textSpan as TextSpan;
    final text = textSpan.text;
    if (text == null) {
      return null;
    }
    endOffset = offset.value + text.length;
    if (offset.value == targetOffset && affinity == TextAffinity.downstream ||
        offset.value < targetOffset && targetOffset < endOffset ||
        endOffset == targetOffset && affinity == TextAffinity.upstream) {
      /// 找到了对应的TextSpan,这个时候就要切分了
      if (offset.value < targetOffset && targetOffset < endOffset) {
        final beforeStr = text.substring(0, targetOffset - offset.value);
        final afterStr = text.substring(targetOffset - offset.value);
        beforeCurrentTextSpan = createSpanFrom(textSpan,
            removeChildren: true, replaceText: beforeStr) as TextSpan;
        afterCurrentTextSpan = createSpanFrom(textSpan,
            removeChildren: true, replaceText: afterStr) as TextSpan;
        // currentTextSpan = textSpan;
      } else if (offset.value == targetOffset &&
          affinity == TextAffinity.downstream) {
        beforeCurrentTextSpan =
            createSpanFrom(textSpan, removeChildren: true, replaceText: "")
                as TextSpan;
        afterCurrentTextSpan =
            createSpanFrom(textSpan, removeChildren: true) as TextSpan;
        // currentTextSpan = textSpan;
      } else if (endOffset == targetOffset &&
          affinity == TextAffinity.upstream) {
        beforeCurrentTextSpan =
            createSpanFrom(textSpan, removeChildren: true) as TextSpan;
        afterCurrentTextSpan =
            createSpanFrom(textSpan, removeChildren: true, replaceText: "")
                as TextSpan;
        // currentTextSpan = textSpan;
      }
      if (beforeCurrentTextSpan != null) {
        _leadingTextSpans.add(beforeCurrentTextSpan);
      }
      if (afterCurrentTextSpan != null) {
        _trailingTextSpans.add(afterCurrentTextSpan);
      }
      return textSpan;
    }
    offset.increment(text.length);
    return null;
  }

TextPosition 从哪里来?

这里用到了前文提到的getPositionForOffset方法,当我们划分好矩形方块之后即可传入矩形的右下角位置获取这个矩形能够放置的TextSpan的TextPosition了。

而前文提到的另一个方法getBoxesForSelection,则是为了更精准判断文本实际可渲染矩形的,另外要注意实际测试过程中发现一些坑,比如textPaint传入最大宽度10来进行布局,但实际得到的宽度可能会大于10,而且可能大于最大宽度还不少,这些问题尚不清楚,读者可自行测试,有了解的可以交流。

注意事项:

上面提到了本文Demo还未完善的内容在这里:由于WidgetSpan的尺寸等信息需要在布局阶段中计算,本次Demo只是给出一个简单的展示,故直接给出了固定的占位信息,如需处理复杂情况,请自行在布局阶段计算,或参考本Demo给出固定信息的方案。

附:

  • 一个本文查阅资料过程中参考的插件:drop_cap_text
  • 本文中的Demo代码

总结

本文探讨的过程中意外保留了TextSpan的兼容性,而不是创建一个纯文本String来自行解析,这样保留了TextSpan固有的手势检测等固有能力,同时也可以保留WidgetSpan的自定义能力,因此在这方面是一个不小的优势。

不过目前是按照分块布局的,而非逐行布局,经过我目前调研逐行布局还是一个挑战。总之这个思路是一个不错的尝试和开端。

后续可能会做的事:

  1. 研究一下多个矩形块的情况
  2. 尝试一下上文提到的思路2的方式逐行绘制
  3. 考虑加上光标,增加可编辑能力
  4. 制作一个可用的插件上传到pub上

往期推荐

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-10-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 群英传 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Flutter 中的图文混排与原理解析
在移动开发中图文混排是十分常见的业务需求,如下图效果所示,本篇将介绍在 Flutter 中的图文混排效果与实现原理。
GSYTech
2020/03/20
3.2K0
Flutter 中的图文混排与原理解析
Flutter文字渲染模块总结(一)
​ 把文字渲染到屏幕上主要是通过加载字体获得字形(Glyph)纹理,然后通过字体测量计算出字体左上角的位置和宽高,然后再把纹理贴到2D方块中。字体的存储主要有两种方式:
用户9172902
2021/11/12
1.4K0
Flutter文字渲染模块总结(一)
Flutter Widgets 之 RichText
应用程序离不开文字的展示,因此文字的排版非常重要,通常情况下Text组件可以完成绝大多数需求,它可以显示不同大小的文字、字体、颜色等,如果想在一句话或者一段文字里面显示不同样式的文字,Text组件无法满足我们的需求,这个时候需要使用RichText。
老孟Flutter
2020/03/01
1.2K0
Flutter Widgets 之 RichText
【Flutter 专题】32 Flutter 32: 图解 TextPainter 与 TextSpan 小尝试
大家在学习 Flutter 时一定会用过 Text,而对于一些复杂文本的处理可能会选择 RichText,再进一步,使用 RichText 就一定要用 TextSpan ,和尚本以为可以做为一个小知识点进行简单学习,但是随着深入尝试发现 TextSpan 涉及东西很多,很值得研究,因此单独整理一篇小博文。
阿策小和尚
2019/08/12
2.1K0
【Flutter 专题】32 Flutter 32: 图解 TextPainter 与 TextSpan 小尝试
带你深入理解 Flutter 中的字体“冷”知识
本篇将带你深入理解 Flutter 开发过程中关于字体和文本渲染的“冷”知识,帮助你理解和增加关于 Flutter 中字体绘制的“无用”知识点。
GSYTech
2020/06/02
1.4K0
带你深入理解 Flutter 中的字体“冷”知识
Flutter TolyUI 框架#03 | 全局消息通知
TolyUI 是 张风捷特烈 打造的 Fluter 全平台应用开发 UI 框架。具备 全平台、组件化、源码开放、响应式 四大特点。可以帮助开发者迅速构建具有响应式全平台应用软件:
张风捷特烈
2024/05/17
2610
Flutter TolyUI 框架#03 | 全局消息通知
Flutter Slider 挂件:配合案例理解
Slider 是一个基本的 Flutter 挂件 - 可以通过移动 slider 的滑块来选择范围值。在 Flutter 中,有不同类型的 slider 挂件,Flutter 框架中常用的有:
Jimmy_is_jimmy
2024/03/17
7130
Flutter Slider 挂件:配合案例理解
Flutter 绘制探索 7 | 不使用 CustomPaint 进行绘制 | 七日打卡
可能说起 Flutter 绘制,大家第一反应就是用 CustomPaint 组件,自定义 CustomPainter 对象来画。Flutter 中所有可以看得到的组件,比如 Text、Image、Switch、Slider 等等,追其根源都是画出来的,但通过查看源码可以发现,Flutter 中绝大多数组件并不是使用 CustomPaint 组件来画的,其实 CustomPaint 组件是对框架底层绘制的一层封装。这个系列便是对 Flutter 绘制的探索,通过测试、调试及源码分析来给出一些在绘制时被忽略或从未知晓的东西,而有些要点如果被忽略,就很可能出现问题。
张风捷特烈
2021/01/20
1.2K0
Flutter 绘制探索 7 | 不使用 CustomPaint 进行绘制 | 七日打卡
Flutter使用Canvas实现精美表盘效果
上个月参加掘金创作者训练营时,发现训练营中的一位兄弟通过 css3 实现了一个精美的表盘,效果看着确实不错很漂亮,跟 UI 做的设计图差不多了, 当时就在想能不能在 Flutter 中实现一个同样的效果,于是趁着周末空闲时间使用 Flutter 的 Canvas 使用了一个同样的效果。
loongwind
2022/09/27
1.5K1
Flutter使用Canvas实现精美表盘效果
Flutter TolyUI 框架#02 | Popover 与 Tooltip 设计
TolyUI 是 张风捷特烈 打造的 Fluter 全平台应用开发 UI 框架。具备 全平台、组件化、源码开放、响应式 四大特点。可以帮助开发者迅速构建具有响应式全平台应用软件:
张风捷特烈
2024/05/17
6830
Flutter TolyUI 框架#02 |  Popover 与 Tooltip 设计
关于Flutter中的RichText组件,你了解多少?
今天给大家带来的是RichText组件,他里面有个text属性,RichText显示的文本内容是TextSpan类型,他不是一个简单的string,而是TextSpan类型,TextSpan类型是一个可以无限传递的树形结构,每个节点出了text属性,还可以通过style属性,设置自定义文字样式。甚至通过children属性,传入一个TextSpan列表作为子节点,已实现叠加和嵌套文字样式的功能。然后大家有没有疑问,关于红色的这个是如何设置的,这个我可以称呼它为碰撞检测,以便完成TextSpan树中某一片段的检测。recognizer: TapGestureRecognizer()这个属性就可以做到,当然,还有一个组件也有类似的功能,是什么呢?GestureDetector,大家可以对他也了解了解。
徐建国
2022/06/24
9550
关于Flutter中的RichText组件,你了解多少?
Flutter——实现微信搜索框
我们要搜索首页数据,所以我们跳转的时候需要把值传递过来。定义数据,和初始化的方法,选择可选的
CC老师
2022/01/14
2.1K0
Flutter——实现微信搜索框
【Flutter实战】文本组件及五大案例
老孟导读:大家好,这是【Flutter实战】系列文章的第二篇,这一篇讲解文本组件,文本组件包括文本展示组件(Text和RichText)和文本输入组件(TextField),基础用法和五个案例助你快速掌握。
老孟Flutter
2020/09/11
7.5K0
【Flutter实战】文本组件及五大案例
Flutter 文本解读 7 | RichText 写个代码高亮组件
@charset "UTF-8";.markdown-body{word-break:break-word;line-height:1.75;font-weight:400;font-size:15px;overflow-x:hidden;color:#333}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{line-height:1.5;margin-top:35px;margin-bottom:10px;padding-bottom:5px}.markdown-body h1:first-child,.markdown-body h2:first-child,.markdown-body h3:first-child,.markdown-body h4:first-child,.markdown-body h5:first-child,.markdown-body h6:first-child{margin-top:-1.5rem;margin-bottom:1rem}.markdown-body h1:before,.markdown-body h2:before,.markdown-body h3:before,.markdown-body h4:before,.markdown-body h5:before,.markdown-body h6:before{content:"#";display:inline-block;color:#3eaf7c;padding-right:.23em}.markdown-body h1{position:relative;font-size:2.5rem;margin-bottom:5px}.markdown-body h1:before{font-size:2.5rem}.markdown-body h2{padding-bottom:.5rem;font-size:2.2rem;border-bottom:1px solid #ececec}.markdown-body h3{font-size:1.5rem;padding-bottom:0}.markdown-body h4{font-size:1.25rem}.markdown-body h5{font-size:1rem}.markdown-body h6{margin-top:5px}.markdown-body p{line-height:inherit;margin-top:22px;margin-bottom:22px}.markdown-body strong{color:#3eaf7c}.markdown-body img{max-width:100%;border-radius:2px;display:block;margin:auto;border:3px solid rgba(62,175,124,.2)}.markdown-body hr{border:none;border-top:1px solid #3eaf7c;margin-top:32px;margin-bottom:32px}.markdown-body code{word-break:break-word;overflow-x:auto;padding:.2rem .5rem;margin:0;color:#3eaf7c;font-weight:700;font-size:.85em;background-color:rgba(27,31,35,.05);border-radius:3px}.markdown-body code,.markdown-body pre{font-family:Menlo,Monaco,Consolas,Courier New,monospace}.markdown-body pre{overflow:auto;position:relative;line-height:1.75;border-radius:6px;border:2px solid #3eaf7c}.markdown-body pre>code{font-size:12px;padding:15px 12px;margin:0;word-break:normal;display:block;overflow-x:auto;color:#333;background:#f8f8f8}.markdown-body a{font-weight:500;text-decoration:none;color:#3eaf7c}.markdown-body a:active,.ma
张风捷特烈
2021/01/27
1.6K0
Flutter 文本解读 7 | RichText 写个代码高亮组件
Flutter 文本解读 6 | RichText 富文本的使用 (中)
@charset "UTF-8";.markdown-body{word-break:break-word;line-height:1.75;font-weight:400;font-size:15px;overflow-x:hidden;color:#333}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{line-height:1.5;margin-top:35px;margin-bottom:10px;padding-bottom:5px}.markdown-body h1:first-child,.markdown-body h2:first-child,.markdown-body h3:first-child,.markdown-body h4:first-child,.markdown-body h5:first-child,.markdown-body h6:first-child{margin-top:-1.5rem;margin-bottom:1rem}.markdown-body h1:before,.markdown-body h2:before,.markdown-body h3:before,.markdown-body h4:before,.markdown-body h5:before,.markdown-body h6:before{content:"#";display:inline-block;color:#3eaf7c;padding-right:.23em}.markdown-body h1{position:relative;font-size:2.5rem;margin-bottom:5px}.markdown-body h1:before{font-size:2.5rem}.markdown-body h2{padding-bottom:.5rem;font-size:2.2rem;border-bottom:1px solid #ececec}.markdown-body h3{font-size:1.5rem;padding-bottom:0}.markdown-body h4{font-size:1.25rem}.markdown-body h5{font-size:1rem}.markdown-body h6{margin-top:5px}.markdown-body p{line-height:inherit;margin-top:22px;margin-bottom:22px}.markdown-body strong{color:#3eaf7c}.markdown-body img{max-width:100%;border-radius:2px;display:block;margin:auto;border:3px solid rgba(62,175,124,.2)}.markdown-body hr{border:none;border-top:1px solid #3eaf7c;margin-top:32px;margin-bottom:32px}.markdown-body code{word-break:break-word;overflow-x:auto;padding:.2rem .5rem;margin:0;color:#3eaf7c;font-weight:700;font-size:.85em;background-color:rgba(27,31,35,.05);border-radius:3px}.markdown-body code,.markdown-body pre{font-family:Menlo,Monaco,Consolas,Courier New,monospace}.markdown-body pre{overflow:auto;position:relative;line-height:1.75;border-radius:6px;border:2px solid #3eaf7c}.markdown-body pre>code{font-size:12px;padding:15px 12px;margin:0;word-break:normal;display:block;overflow-x:auto;color:#333;background:#f8f8f8}.markdown-body a{font-weight:500;text-decoration:none;color:#3eaf7c}.markdown-body a:active,.ma
张风捷特烈
2021/01/26
3K0
Flutter 文本解读 6 | RichText 富文本的使用 (中)
Flutter 文字解读 5 | RichText 富文本的使用 (上)
通过前四篇,我们已经了解了 Text 的源码实现和基本使用方式。其本质是使用了 RichText进行构建的,也就是说认识了 Text 就等价于认识了 RichText 。通过 Text.rich 我们也可以方便地构建富文本组件,在第三篇中介绍了一下 Text.rich,本篇就来详细地介绍一下富文本的使用。本篇和之前的几篇关系不大,可单独食用。
张风捷特烈
2021/01/26
7.5K0
【Flutter 专题】137 图解自定义 ACEFoldTextView 折叠文本
和尚在学习 Flutter 过程中,有特别需求是对于文本过长的内容需要展示固定行数,而在文本右下角有提示用户点击展开和收起;和尚尝试自定义一个可折叠收缩的 ACEFoldTextView;
阿策小和尚
2021/10/12
1.4K0
Flutter自定义view —— 闯关进度条
但我觉得还是用自定义 view 实现效果比较好,想要什么效果都可以去实现,所以我按照
CatEatFish
2020/07/09
1K0
Flutter自定义view —— 闯关进度条
Flutter学习之视图体系
经过之前的学习,可以知道Flutter是一种全新的响应式跨平台的移动开发框架,越来越多的开发者参与学习或者研究中,确实在iOS和Android平台上能够用一套代码构建出性能比较高的应用程序。我刚开始接触FlutterFlutter中文网看到这么一句话:Widget是Flutter应用程序用户界面的基本构建块。每个Widget都是用户界面一部分的不可变声明。与其他将试图、控制器、布局和其他属性分离的框架不同,Flutter具有一致的统一对象模型:Widget。在开发过程中也可以知道Widget可以被定义按钮(button)、样式(style)、填充(Padding)、布局(Row)、手势(GestureDetector)等,我刚开始以为这个Widget就是眼中所看到的视图,然而并不是这样的,下面慢慢讲述。
Android技术干货分享
2019/04/01
1.6K0
Flutter学习之视图体系
算法遇记 | 字符串段拆插问题 - 富文本
最近遇到一个小问题,这里把问题模型简化,记录一下处理方式,也算是一个小纪念。先说一下场景,如下所示:
张风捷特烈
2022/12/15
4340
算法遇记 | 字符串段拆插问题 - 富文本
相关推荐
Flutter 中的图文混排与原理解析
更多 >
交个朋友
加入HAI高性能应用服务器交流群
探索HAI应用新境界 共享实践心得
加入腾讯云技术交流站
洞悉AI新动向 Get大咖技术交流群
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验