Flutter 进阶:RichText/TextSpan 富文本终极版

2,842 阅读2分钟

一、需求来源

工作中经常遇到一些富文本的样式,每次写许多的 TextSpan 的不胜其烦;今天终于想到一种完美处理高亮文本样式和段落样式的办法,核心是通过 splitMapJoin 方法配合 RegExp,代码极其精简,使用极度舒适。

二、使用对比

1、原始代码

Text.rich(
    TextSpan(
      children: [
        TextSpan(
            text: "普通文本1",
            style: const TextStyle(
              fontSize: 14,
              fontWeight: FontWeight.w500,
              color: Color(0xFF1A1A1A),
            ),
        ),
        TextSpan(
            text: "高亮1",
            style: const TextStyle(
              fontSize: 14,
              fontWeight: FontWeight.w500,
              color: Color(0xFF737373),
            ),
        ),
         TextSpan(
            text: "普通文本2",
            style: const TextStyle(
              fontSize: 14,
              fontWeight: FontWeight.w500,
              color: Color(0xFF737373),
            ),
        ),
        TextSpan(
            text: "高亮2",
            style: const TextStyle(
              fontSize: 14,
              fontWeight: FontWeight.w500,
              color: Color(0xFF737373),
            ),
        ),
         TextSpan(
            text: "普通文本3",
            style: const TextStyle(
              fontSize: 14,
              fontWeight: FontWeight.w500,
              color: Color(0xFF737373),
            ),
        ),
      ],
    ),
  ),

2、封装后代码

final text = "普通文本1高亮1普通文本2高亮2普通文本3高亮3普通文本4";
final textTaps = ["高亮1", "高亮2", "高亮3"];
...

Text.rich(
  TextSpan(
    children: RichTextExt.createTextSpans(
      text: text,
      textTaps: textTaps,
      // linkStyle: TextStyle(fontSize: 18.0, color: Colors.red),
      onLink: (textTap) {
        ddlog(textTap);
      },
    ),
  ),
),

三、实现源码(35 行)

extension RichTextExt on RichText {
  /// 创建 List<TextSpan>
  ///
  /// text 整个段落
  /// textTaps 高亮字符串数组
  /// style 段落样式
  /// linkStyle 高亮样式
  /// prefix 切割符号,避免和文章包含字符串重复
  /// suffix 切割符号,避免和文章包含字符串重复
  /// onLink 高亮部分点击事件
  static List<TextSpan> createTextSpans({
    required String text,
    required List<String> textTaps,
    TextStyle? style,
    TextStyle? linkStyle,
    String prefix = "_&t",
    String suffix = "_&t",
    required void Function(String textTap) onLink,
  }) {
    final pattern = textTaps.map((d) => RegExp.escape(d)).join('|');
    final regExp = RegExp(pattern, multiLine: true);
    final textNew = text.splitMapJoin(regExp,
      onMatch: (m) => '$prefix${m[0]}$suffix', // (or no onMatch at all)
      onNonMatch: (n) => n,
    );

    final list = textNew.split(RegExp('$prefix|$suffix'));
    return list.map((e) {
      if (e.isNotEmpty) {
        final isEquel = textTaps.contains(e);
        if (isEquel) {
          return TextSpan(
            text: e,
            style: linkStyle ?? TextStyle(color: Colors.blue),
            recognizer: TapGestureRecognizer()
              ..onTap = () {
                onLink(e);
              },
          );
        }
      }
      return TextSpan(text: e, style: style);
    }).toList();
  }

}

四、总结

1、目前没有找比这更加优雅的,同时支持高亮文本点击事件的代码实现,完全是灵光一闪的杰作。(如果有,请一定联系我!!!)
2、onLink 是高亮文本点击回调,推荐开始用一个Map保存高亮文字和对应的链接;例如:
var linkMap = {
  '《用户协议》': 'https://flutter.dev',
  '《隐私政策》': 'https://flutter.dev',
};
Text.rich(
  TextSpan(
    children: RichTextExt.createTextSpans(
      text: text,
      textTaps: linkMap.keys.toList(),
      // linkStyle: TextStyle(fontSize: 18.0, color: Colors.red),
      onLink: (textTap) {
        ddlog(textTap);// linkMap[textTap]即为相对应的协议链接
      },
    ),
  ),
),
3、原创就是开心啦啦啦。

github