Flutter文本解析,轻松消息卡片实现链接,表情,命令解析

需求

我们在做消息卡片的时候,经常需要对文本进行解析,来显示不同的状态,如下图,比如 @, 表情, 链接等,需要解析的内容可能颜色值不一样,可能可以点击。这类需求看着复杂,其实就是一个纸老虎,需要掌握的知识并不多,话不多说,我们看技术实现。

image.png

知识储备

1. 正则表达

本文以解析文本中的链接,表情,邮箱地址作为示例。正则表达式我就直接放在下面,可以直接使用,对于其他的解析类型,我推荐一个网站,大家可以在这个网站测试学习。

邮箱

const emailPattern = RegExp(r"\b[\w\.-]+@[\w\.-]+\.\w{2,4}\b");

链接

const urlPattern =
    RegExp(
      r"(http(s)?)://[a-zA-Z\d@:._+~#=-]{1,256}\.[a-z\d]{2,18}\b([-a-zA-Z\d!@:_+.~#?&/=%,$]*)(?<![$])");

表情, 表情一般都是使用中括号[]实现,比如:[笑脸]

const emoPattern = RegExp(r"\[(.*?)\]");

2.文本正则匹配

Dart的String类型,提供了一个方法 splitMapJoin, 这个方法可以根据正则匹配,把匹配的结果分段回调给使用者。
比如我们要解析文字开头那一段文字,我们可以按照如下的方式来写。

 final _mapping = [
      urlPattern.pattern,
      emoPattern.pattern,
      atPatternIncomplete.pattern,
      emailPattern.pattern,
    ];
final pattern = '(${_mapping.join('|')})';
    
final newString =
        "@!280849192149057536你好[爱心][呲牙] 看链接 https://mp.weixin.qq.com/s/60N9gqRXKjsTuA5ZvdZiiA";

newString.splitMapJoin(
  RegExp(
    pattern,
    multiLine: false,
    caseSensitive: true,
    dotAll: true,
    unicode: false,
  ),
  onMatch: (Match match) {
     final matchText = match[0];
    debugPrint("onMatch: $matchText");
    return "$matchText";
  },
  onNonMatch: (String text) {
    debugPrint("onNonMatch: $text");

    return text;
  },
);

有兴趣的可以可以自己跑一下,我把结果打印出了,大家可以看看。看到这个结果,有Flutter基础的人,应该直接下来该怎么做了。使用RichText结合TextSpan,就可以实现前文的需求了。

flutter: onNonMatch:
flutter: onMatch: @!280849192149057536
flutter: onNonMatch: 你好
flutter: onMatch: [爱心]
flutter: onNonMatch:
flutter: onMatch: [呲牙]
flutter: onNonMatch:  看链接
flutter: onMatch: https://mp.weixin.qq.com/s/60N9gqRXKjsTuA5ZvdZiiA
flutter: onNonMatch:

实现

其实上条已经说了实现方式,不过我们还是继续来看看。使用Flutter提供的RichText组件,使用RichText结合TextSpan``和WidgetSpan,就可以很方便实现这个需求。

TextSpan 用来实现局部文字加粗,设置颜色显示。WidgetSpan用来向文本段落中嵌入其他如小图片、图标、按钮等。

现在我们来实现前文的需求。

  1. 新建一个Widget数组,用来储存匹配结果
List<InlineSpan> widgets = [];
  1. 对于没匹配成功的,修改前文中的onNonMatch方法。
onNonMatch: (String text) {
  debugPrint("onNonMatch: $text");

  widgets.add(TextSpan(
      text: text,
      style: const TextStyle(color: Colors.black, fontSize: 14)));

  return text;
},
  1. 对于匹配成功的,修改前文中的onMatch方法。逻辑也不复制,就是根据匹配到的正则,做不同的处理就行,具体可以看如下代码。
onMatch: (Match match) {
  final matchText = match[0];

  debugPrint("onMatch: $matchText");
  if (urlPattern.hasMatch(matchText!)) {
   //链接解析
    widgets.add(WidgetSpan(
      alignment: PlaceholderAlignment.middle,
      child: GestureDetector(
          onTap: () {},
          child: Text(
            matchText,
            style:
                const TextStyle(color: Colors.lightGreen, fontSize: 14),
          )),
    ));
  } else if (emoPattern.hasMatch(matchText)) {
    // 表情解析
    //allEmoMap 包含表情和表情路径的字典, 如 allEmoMap = {"笑脸": "path/to/xxx"};
    final asset = allEmoMap[matchText!];
    widgets.add(WidgetSpan(
      alignment: PlaceholderAlignment.middle,
      child: GestureDetector(
          onTap: () {},
          child: asset == null
              ? Text(matchText)
              : Image.asset(
                  asset,
                  width: 14,
                  height: 14,
                )),
    ));
  } else if (emailPattern.hasMatch(matchText)) {
   //邮箱解析
    widgets.add(TextSpan(
        text: matchText,
        style: const TextStyle(color: Colors.blue, fontSize: 14)));
  } else if (atPatternIncomplete.hasMatch(matchText)) {
    //AT 解析。把其中的用户id,解析成用户昵称,需要接口,本文就不写了
  } else {
    //其他情况
    widgets.add(TextSpan(
        text: matchText,
        style: const TextStyle(color: Colors.black, fontSize: 14)));
  }

  return matchText;
},
  1. 最后,只需要把解析的Widget数组,放到RichText中就成了。
return RichText(
 text: TextSpan(
   text: '',
   children: <InlineSpan>[...widgets],
 ),
);

结束语

在Flutter项目中遇到这类需求,看完这个文章,相信大家都有思路了,不过文章只提供了实现思路,真实项目最好还是在封装一下。让使用方调用更加简单,方法很多,就话不多说了。

© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享
相关推荐
  • 暂无相关文章
  • 评论 抢沙发
    头像
    欢迎您留下宝贵的见解!
    提交
    头像

    昵称

    取消
    昵称表情代码图片

      暂无评论内容