Flutter 文本解读 6 | RichText 富文本的使用 (中)


主题列表:juejin, github, smartblue, cyanosis, channing-cyan, fancy, hydrogen, condensed-night-purple, greenwillow, v-green, vue-pro, healer-readable, mk-cute, jzman, geek-black, awesome-green, qklhk-chocolate

贡献主题:https://github.com/xitu/juejin-markdown-themes

theme: v-green

highlight:

零、前言

上篇中,通过文本解析,实现了对指定文字的高亮包裹,如下图。今天我们继续完善这个富文本显示的功能,比如文本链接解析文本标题指定文字加粗、斜体 等。本文会用到一些正则表达式的知识,本系列重点不是正则,不会做过多解释。如果看不懂,可以自己去补补。

以下是 Flutter 文本解读 系列的其他文章:


一、文本链接的处理
1.链接匹配的正则

通过 \[.*?\) 就可以匹配出 markdown 中的链接,这样就可以通过 StringScanner 获取每个匹配到的起始索引。之后的事就和之前一样了。


2.对数据的抽象与实现

可以看出,需要解析的类型是需要拓展的。不同情况的处理也不相同,这样的话,我们可以创建个枚举类,然后根据类型进行判断处理,但这样很多逻辑都会塞在一块,不好维护。我们可以定义一层抽象,分离出属性和行为,再根据不同的情况进行不同的实现,使用时使用抽象类完成任务即可。

如下抽象中,需要的数据是一段字符的起止所以,子类需要实现 text 方法返回展示的字符,实现 style 方法获取文字样式。提供 recognizer 属性进行事件处理。

```dart abstract class SpanBean { SpanBean(this.start, this.end,{this.recognizer});

final int start; final int end;

String text(String src);

TextStyle get style;

final GestureRecognizer recognizer; } ```


这样可以通过 WrapSpanBean 实现之前的包裹高亮,代码如下:

``dart //包裹规则:data` class WrapSpanBean extends SpanBean { WrapSpanBean(int start, int end) : super(start, end);

@override String text(String src) { return src.substring(start + 1, end - 1); }

@override TextStyle get style => TextStyleSupport.dotWrapStyle; } ```

在使用时,使用抽象 SpanBean ,在列表添加对象时使用对应的实现。这便是多态的奥义

```dart List _spans = []; // 使用抽象

void parseContent() { while (!scanner.isDone) { if (scanner.scan(RegExp('.*?'))) { int startIndex = scanner.lastMatch.start; int endIndex = _scanner.lastMatch.end; _spans.add(WrapSpanBean(startIndex, endIndex)); // 添加实现 } if (!scanner.isDone) { _scanner.position++; } } } ```


3.链接解析

处理链接数据的 LinkSpanBean 实现 SpanBean

```dart //链接规则: data class LinkSpanBean extends SpanBean { LinkSpanBean(int start, int end, {GestureRecognizer recognizer}) : super(start, end, recognizer: recognizer);

@override TextStyle get style => TextStyleSupport.linkStyle;

@override String text(String src) { final String target = src.substring(start, end); return target.split(']')[0].replaceFirst("[", ''); } } ```

parseContent 中收录 LinkSpanBean,其点击事件通过 url_launcher: ^5.7.10 插件跳转到浏览器。有一点要注意: GestureRecognizer 需要被 dispose,可以在 StringParser 中定义 dispose 来遍历 SpanBean 列表进行释放。

``dart void parseContent() { while (!_scanner.isDone) { if (_scanner.scan(RegExp('.*?`'))) { int startIndex = _scanner.lastMatch.start; int endIndex = _scanner.lastMatch.end; _spans.add(WrapSpanBean(startIndex, endIndex)); }

if (_scanner.scan(RegExp(r'\[.*?\)'))) {
  int startIndex = _scanner.lastMatch.start;
  int endIndex = _scanner.lastMatch.end;
  final String target = content.substring(startIndex, endIndex);
  String link = target.split('(')[1].replaceFirst(')', '');
  GestureRecognizer recognizer = TapGestureRecognizer()
    ..onTap = () {
      launch(link);
    };
  _spans.add(LinkSpanBean(startIndex, endIndex, recognizer: recognizer));
}

if (!_scanner.isDone) {
  _scanner.position++;
}

} }

void dispose() { _spans.forEach((element) { element.recognizer?.dispose(); }); } ```


4.TextSpan 处理

和之前的处理一样,这里我们为 SpanBean 添加了GestureRecognizer,在生成 TextSpan 时使用一下即可。

```dart InlineSpan parser() { _scanner = StringScanner(content); parseContent(); final List spans = []; int currentPosition = 0; for (SpanBean span in _spans) { if (currentPosition != span.start) { spans.add( TextSpan(text: content.substring(currentPosition, span.start))); }

spans.add(TextSpan(
    style: span.style,
    text: span.text(content),
    recognizer: span.recognizer));
currentPosition = span.end;

}

if (currentPosition != content.length) spans.add( TextSpan(text: content.substring(currentPosition, content.length))); return TextSpan(style: TextStyleSupport.defaultStyle, children: spans); } ```


5.使用效果

这样便可以实现下面的将文本中的链接高亮。

并且点击链接时可以进行跳转。


二、标题文字的处理
1.标题匹配的正则

通过 ^#+ .* 来匹配 若干个 # 的开头的行。 在 Dart 正则中多行的开头匹配需要。multiLine: true 。这样如下的 # 777 就不会被误配。

dart RegExp(r'^#+ .*',multiLine: true)


2.HeadSpanBean 定义

HeadSpanBean 作为 SpanBean 的实现类,可以完成六个等级的标题,通过 lever 属性表示是几级标题。

```dart //标题规则: #+ data class HeadSpanBean extends SpanBean{

HeadSpanBean(int start, int end,this.lever) : super(start, end);

final int lever;

@override TextStyle get style => TextStyleSupport.headStyleMap[lever];

@override String text(String src) { final String target = src.substring(start, end); return target.replaceRange(0, lever + 1, ''); } } ```

TextStyleSupport 中提供一个 headStyleMap 用于根据数字获取样式,这样就不需要用分支结构去逐条返回,让代码看着更舒服些。

```dart class TextStyleSupport { static const TextStyle defaultStyle = TextStyle(color: Colors.black, fontSize: 14);

static const TextStyle dotWrapStyle = TextStyle(color: Colors.purple, fontSize: 14);

static const TextStyle linkStyle = TextStyle( color: Colors.blue, decoration: TextDecoration.underline, decorationColor: Colors.blue);

static const Map headStyleMap = { 1:h1, 2:h2, 3:h3, 4:h4, 5:h5, 6:h6, };

static const TextStyle h1 = TextStyle(color: Colors.black, fontSize: 24, fontWeight: FontWeight.bold); static const TextStyle h2 = TextStyle(color: Colors.black, fontSize: 22, fontWeight: FontWeight.bold); static const TextStyle h3 = TextStyle(color: Colors.black, fontSize: 20, fontWeight: FontWeight.bold); static const TextStyle h4 = TextStyle(color: Colors.black, fontSize: 18, fontWeight: FontWeight.bold); static const TextStyle h5 = TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold); static const TextStyle h6 = TextStyle(color: Colors.black, fontSize: 14, fontWeight: FontWeight.bold); } ```


3.标题的解析

这样通过 ^#+ .* 正则表达式,获取对应字符区间的前后界,再分析有多少个 # 即可。

dart if (_scanner.scan(RegExp(r'^#+ .*',multiLine: true))) { int startIndex = _scanner.lastMatch.start; int endIndex = _scanner.lastMatch.end; int lever = content.substring(startIndex, endIndex).split(' ')[0].length; _spans.add(HeadSpanBean(startIndex, endIndex,lever)); }

这样以 # 开头的标题样式就完成了。在 TextStyleSupport 中你可以修改这些默认的样式。或者提供多组不同的样式,提供切换。知道其中的原理,可操作性就可以大大提高。


三、文字加粗和倾斜
1.文字加粗处理

markdown加粗的规则是 **data**,通过之前的那几个,现在应该知道大致流程了。对应的正则是 \*\*.*?\*\*

定义 BoldSpanBean 如下 :

dart //加粗规则: **data** class BoldSpanBean extends SpanBean{ BoldSpanBean(int start, int end) : super(start, end); @override TextStyle get style => TextStyleSupport.bold; @override String text(String src) { return src.substring(start+2, end-2); } }


解析内容时,进行添加 BoldSpanBean 即可。

dart if (_scanner.scan(RegExp(r'\*\*.*?\*\*'))) { int startIndex = _scanner.lastMatch.start; int endIndex = _scanner.lastMatch.end; _spans.add(BoldSpanBean(startIndex, endIndex)); }

这样就可以实现局部文字加粗的效果:


2.文字倾斜处理

markdown倾斜的规则是 *data*。对应的正则是 \*\*.*?\*\*,这时我们会发现,这样加粗的 **data** 会有所干扰,使用在解析时,可以先解析 加粗 ,再解析 倾斜。因为 StringScanner 只会对文本进行一次扫描,加粗 的扫描完后,位置索引会增加,就不会对 倾斜 的正则产生影响。

```dart // 加粗匹配 if (_scanner.scan(RegExp(r'**.*?**'))) { int startIndex = _scanner.lastMatch.start; int endIndex = _scanner.lastMatch.end; _spans.add(BoldSpanBean(startIndex, endIndex)); }

// 倾斜匹配 if (_scanner.scan(RegExp(r'*.*?*'))) { int startIndex = _scanner.lastMatch.start; int endIndex = _scanner.lastMatch.end; _spans.add(LeanSpanBean(startIndex, endIndex)); } ```

```dart //加粗规则: data class LeanSpanBean extends SpanBean{

LeanSpanBean(int start, int end) : super(start, end);

@override TextStyle get style => TextStyleSupport.lean;

@override String text(String src) { return src.substring(start+1, end-1); } } ```

通过本篇,你应该对富文本的使用多了些了解。这样看来,新加一个规则,最重要的是找到其对应的正则表达式。找到之后,就是一些简单的处理了。本文就到这里,下一篇来看一下,在 Flutter 中如何实现一个代码高亮显示的富文本。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值