Flutter 中 RichText、SelectableText 的使用详解 | Flutter Widgets

4,895 阅读5分钟

这是我参与更文挑战的第25天,活动详情查看: 更文挑战

前言

前面我们聊了 Flutter 中 Text 各种属性样式调配的展示效果,这篇我们稍微深入一点,看看 Text 的源码以及富文本的使用,最后聊聊选择文本的应用。

image.png

RichText

Text 源码

查看富文本之前,我们先看看上篇的 Text 源码,其实在上上篇我们演示行高参数的时候就可以看到虽然我们使用的是 Text ,但是渲染时在 Widget 树中看到的是 RichText
image.png

Text 源码一览

出现上述的原因是什么呢?我们来瞅瞅源码吧
image.png
首先 Text 继承自 StatelessWidget ,下面我们就直接看 build 方法即可。

这里看到上面注释圈起来的部分,说 RichText 可以更好的让我们控制文字样式

  • Text - build 方法

image.png
上面👆🏻可以看出 build 方法最终返回的是 result (550 行),而 result 创建的就是 RichText 对象,一般我们没有给文字添加语义标签 semanticsLabel ,所以就会直接返回 RichText Widget 了。

现在我们就知道为啥我们写的是 Text ,但实际是 RichText 的 Widget 的原因了

简单富文本

上面我们知道了原因,下面我们就从一个简单的富文本来走进 RichText
image.png
👆🏻上面的效果,如果单独使用 Text 写是不是我们要外部嵌套一个 Row+3个Text , 我们还要考虑换行问题,这样就比较麻烦了,如果我们使用 RichText,就不需要考虑这么多,关心样式即可。

RichText(
  text: TextSpan(
    text: 'Flutter Widgets 已有 ',
    children: [
      TextSpan(
        text: '25',
        // 设置个性化覆盖样式
        style: TextStyle(
          fontWeight: FontWeight.bold,
          fontSize: 18,
        ),
      ),
      // 复用上层通用样式
      TextSpan(text: ' 系列文章'),
    ],
    // 设置通用样式
    style: TextStyle(
      color: Colors.blue,
    ),
  ),
)

这里我们主要关心样式的设置,有 2 个点需要注意:

  • 思想上要理解好这种 TextSpan 的嵌套关系
  • 要理解 style 的继承和覆盖关系

TextSpan

上面我们使用到了 TextSpan ,来一起看看他的属性吧。

上源码

image.png
这里我们看到 TextSpan 继承自 InlineSpan ,实现了 HitTestTarget(命中检测)和 MouseTrackerAnnotation (鼠标追踪),属性上也非常的简单,常用的可能就是前 4 个,然后前 3 个我们都了解使用过了,下面来看看第四个吧。

局部手势检测

我现在把 25 这个数字变成红色,并且要加上点击事件怎么办呢?这时候就用到了第 4 个属性 recognizer

// 点击手势检测器
late TapGestureRecognizer tapRecognizer;
@override
void initState() {
  tapRecognizer = TapGestureRecognizer()..onTap = _handleTap;
  super.initState();
}

/// 处理点击
void _handleTap() {
  // 打印
  print('tapRecognizer 25');
  // 设置震动效果
  HapticFeedback.vibrate();
}
// 这里只贴图 25 的部分代码
TextSpan(
  text: '25',
  // 设置个性化覆盖样式
  style: TextStyle(
    fontWeight: FontWeight.bold,
    fontSize: 18,
    color: Colors.red,
  ),
  // 设置点击检测器
  recognizer: tapRecognizer,
)
  • 显示效果

image.png

  • 点击后打印如下

image.png
这样我们再做,比如用户协议与隐私政策的时候就可以用到啦,可以做单独的样式和跳转。

WidgetSpan

上面我们还只是说了富文本中文字多样式的情况,还有嵌套各种图片的情况,在实际富文本应用时比这个要复杂的多,文章中示例做核心的解法,具体还需要实际应用和创新。
image.png
如果在上面的基础上我们需要加上图标怎么处理呢?看看下面的代码

RichText(
  text: TextSpan(
    children: [
      // Flutter Logo
      WidgetSpan(child: FlutterLogo()),
      TextSpan(text: 'Flutter Widgets 已有 '),
      TextSpan(
        text: '25',
        // 设置个性化覆盖样式
        style: TextStyle(
          fontWeight: FontWeight.bold,
          fontSize: 18,
          color: Colors.red,
        ),
        recognizer: tapRecognizer,
      ),
      // 链接图标
      WidgetSpan(
        child: Icon(
          Icons.link,
          size: 16,
          color: Colors.blue,
        ),
      ),
      // 复用上层通用样式
      TextSpan(text: ' 系列文章'),
      // 复制图标
      WidgetSpan(
        child: Icon(
          Icons.copy,
          size: 16,
          color: Colors.grey,
        ),
      ),
    ],
    // 设置通用样式
    style: TextStyle(
      color: Colors.blue,
    ),
  ),
)

这里只是演示,实际项目中会根据下发的文本协议来解析,然后生成对应的 Widget 组合

Text.rich

另外一种创建富文本的方式就是使用 Text.rich ,下面来一起看看代码吧

Text.rich(
  TextSpan(
    children: [
      WidgetSpan(child: FlutterLogo()),
      TextSpan(text: 'Flutter Widgets 已有 '),
      TextSpan(
        text: '25',
        // 设置个性化覆盖样式
        style: TextStyle(
          fontWeight: FontWeight.bold,
          fontSize: 18,
          color: Colors.red,
        ),
        recognizer: tapRecognizer,
      ),
      WidgetSpan(
        child: Icon(
          Icons.link,
          size: 16,
          color: Colors.blue,
        ),
      ),
      // 复用上层通用样式
      TextSpan(text: ' 系列文章'),
      WidgetSpan(
        child: Icon(
          Icons.copy,
          size: 16,
          color: Colors.grey,
        ),
      ),
    ],
    // 设置通用样式
    style: TextStyle(
      color: Colors.blue,
    ),
  ),
)

image.png

小总结

到这里 RichText 基本就聊完了,基础的样式我们在前 2 篇文章已经聊过,可以在专栏中回看。

SelectableText

上面展示的文本都是无法选择复制的,在实际项目中,有些内容文字是需要支持用户可以选择复制的,这时我们就需要将 Text 改变成 SelectableText
image.png

代码展示

SelectableText(
  "Text - ZeroFlutter",
  style: TextStyle(
    // 颜色蓝色
    color: Colors.blue,
    // 字号 24
    fontSize: 24,
  ),
)

使用限制

在实际使用时,你会发现无法指定溢出样式了,默认就是直接剪裁掉了。
image.png
因为考虑到,复制肯定是对所有文本进行操作,如果我们溢出样式是... 那么复制出来就是展示的样式,肯定不符合需求。
所以在实际项目中,如果是长按复制全部可以使用 Text ,选择复制可以 SelectableText

SelectableText.rich

Text.rich 一致,要使用富文本时,可以直接使用 SelectableText.rich

但是这里有个显示就是我们不能使用 WidgetSpan 了,只能使用 TextSpan

SelectableText.rich(
  TextSpan(
    children: [
      // WidgetSpan(child: FlutterLogo()),
      TextSpan(text: 'Flutter Widgets 已有 '),
      TextSpan(
        text: '25',
        // 设置个性化覆盖样式
        style: TextStyle(
          fontWeight: FontWeight.bold,
          fontSize: 18,
          color: Colors.red,
        ),
        recognizer: tapRecognizer,
      ),
      // WidgetSpan(
      //   child: Icon(
      //     Icons.link,
      //     size: 16,
      //     color: Colors.blue,
      //   ),
      // ),
      // 复用上层通用样式
      TextSpan(text: ' 系列文章'),
      // WidgetSpan(
      //   child: Icon(
      //     Icons.copy,
      //     size: 16,
      //     color: Colors.grey,
      //   ),
      // ),
    ],
    // 设置通用样式
    style: TextStyle(
      color: Colors.blue,
    ),
  ),
)

image.png

源码仓库

基于 Flutter 🔥 最新版本

参考链接

关注专栏

  • 此文章已收录到下面👇 的专栏,可以直接关注
  • 更多文章继续阅读|系列文章持续更新

👏 欢迎点赞➕收藏➕关注,有任何问题随时在下面👇评论,我会第一时间回复哦