Flutter文本解析,轻松消息卡片实现链接,表情,命令解析
需求
我们在做消息卡片的时候,经常需要对文本进行解析,来显示不同的状态,如下图,比如 @, 表情, 链接等,需要解析的内容可能颜色值不一样,可能可以点击。这类需求看着复杂,其实就是一个纸老虎,需要掌握的知识并不多,话不多说,我们看技术实现。
知识储备
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
用来向文本段落中嵌入其他如小图片、图标、按钮等。
现在我们来实现前文的需求。
- 新建一个Widget数组,用来储存匹配结果
List<InlineSpan> widgets = [];
- 对于没匹配成功的,修改前文中的
onNonMatch
方法。
onNonMatch: (String text) {
debugPrint("onNonMatch: $text");
widgets.add(TextSpan(
text: text,
style: const TextStyle(color: Colors.black, fontSize: 14)));
return text;
},
- 对于匹配成功的,修改前文中的
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;
},
- 最后,只需要把解析的Widget数组,放到RichText中就成了。
return RichText(
text: TextSpan(
text: '',
children: <InlineSpan>[...widgets],
),
);
结束语
在Flutter项目中遇到这类需求,看完这个文章,相信大家都有思路了,不过文章只提供了实现思路,真实项目最好还是在封装一下。让使用方调用更加简单,方法很多,就话不多说了。
转载自:https://juejin.cn/post/7166474974142660616