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

零、前言

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

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

《Flutter 文本解读 1 | 从源码认识 Text 组件》

《Flutter 文本解读 2 | Text 是如何画出来的》

《Flutter 文本解读 3 | Text 组件使用介绍 》

《Flutter 文本解读 4 | TextStyle 文字样式解读 》

《Flutter 文本解读 5 | RichText 富文本的使用 (上)》

一、文本链接的处理

1.链接匹配的正则

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

2.对数据的抽象与实现

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

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

abstractclassSpanBean{  SpanBean(this.start,this.end,{this.recognizer});finalintstart;finalintend;Stringtext(Stringsrc);  TextStylegetstyle;finalGestureRecognizer recognizer;}复制代码

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

//包裹规则: `data` classWrapSpanBeanextendsSpanBean{  WrapSpanBean(intstart,intend) :super(start, end);@overrideStringtext(Stringsrc) {returnsrc.substring(start +1, end -1);  }@overrideTextStylegetstyle => TextStyleSupport.dotWrapStyle;}复制代码

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

List _spans = [];// 使用抽象voidparseContent() {while(!_scanner.isDone) {if(_scanner.scan(RegExp('`.*?`'))) {intstartIndex = _scanner.lastMatch.start;intendIndex = _scanner.lastMatch.end;      _spans.add(WrapSpanBean(startIndex, endIndex));// 添加实现}if(!_scanner.isDone) {      _scanner.position++;    }  }}复制代码

3.链接解析

处理链接数据的LinkSpanBean实现SpanBean。

//链接规则: [data](link)classLinkSpanBeanextendsSpanBean{  LinkSpanBean(intstart,intend, {GestureRecognizer recognizer})      :super(start, end, recognizer: recognizer);@overrideTextStylegetstyle => TextStyleSupport.linkStyle;@overrideStringtext(Stringsrc) {finalStringtarget = src.substring(start, end);returntarget.split(']')[0].replaceFirst("[",'');  }}复制代码

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

voidparseContent() {while(!_scanner.isDone) {if(_scanner.scan(RegExp('`.*?`'))) {intstartIndex = _scanner.lastMatch.start;intendIndex = _scanner.lastMatch.end;      _spans.add(WrapSpanBean(startIndex, endIndex));    }if(_scanner.scan(RegExp(r'\[.*?\)'))) {intstartIndex = _scanner.lastMatch.start;intendIndex = _scanner.lastMatch.end;finalStringtarget = content.substring(startIndex, endIndex);Stringlink = target.split('(')[1].replaceFirst(')','');      GestureRecognizer recognizer = TapGestureRecognizer()        ..onTap = () {          launch(link);        };      _spans.add(LinkSpanBean(startIndex, endIndex, recognizer: recognizer));    }if(!_scanner.isDone) {      _scanner.position++;    }  }}voiddispose() {  _spans.forEach((element) {    element.recognizer?.dispose();  });}复制代码

4.TextSpan 处理

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

InlineSpan parser() {  _scanner = StringScanner(content);  parseContent();finalList spans = [];intcurrentPosition =0;for(SpanBean spanin_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)));returnTextSpan(style: TextStyleSupport.defaultStyle, children: spans);}复制代码

5.使用效果

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

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

二、标题文字的处理

1.标题匹配的正则

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

RegExp(r'^#+ .*',multiLine:true)复制代码

2.HeadSpanBean 定义

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

//标题规则: #+ dataclassHeadSpanBeanextendsSpanBean{  HeadSpanBean(intstart,intend,this.lever) :super(start, end);finalintlever;@overrideTextStylegetstyle => TextStyleSupport.headStyleMap[lever];@overrideStringtext(Stringsrc) {finalStringtarget = src.substring(start, end);returntarget.replaceRange(0, lever +1,'');  }}复制代码

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

classTextStyleSupport{staticconstTextStyle defaultStyle =      TextStyle(color: Colors.black, fontSize:14);staticconstTextStyle dotWrapStyle =      TextStyle(color: Colors.purple, fontSize:14);staticconstTextStyle linkStyle = TextStyle(      color: Colors.blue,      decoration: TextDecoration.underline,      decorationColor: Colors.blue);staticconstMap  headStyleMap = {1:h1,2:h2,3:h3,4:h4,5:h5,6:h6,  };staticconstTextStyle h1 =      TextStyle(color: Colors.black, fontSize:24, fontWeight: FontWeight.bold);staticconstTextStyle h2 =      TextStyle(color: Colors.black, fontSize:22, fontWeight: FontWeight.bold);staticconstTextStyle h3 =      TextStyle(color: Colors.black, fontSize:20, fontWeight: FontWeight.bold);staticconstTextStyle h4 =      TextStyle(color: Colors.black, fontSize:18, fontWeight: FontWeight.bold);staticconstTextStyle h5 =      TextStyle(color: Colors.black, fontSize:16, fontWeight: FontWeight.bold);staticconstTextStyle h6 =      TextStyle(color: Colors.black, fontSize:14, fontWeight: FontWeight.bold);}复制代码

3.标题的解析

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

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

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

三、文字加粗和倾斜

1.文字加粗处理

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

定义 BoldSpanBean 如下 :

//加粗规则: **data**classBoldSpanBeanextendsSpanBean{  BoldSpanBean(intstart,intend) :super(start, end);@overrideTextStylegetstyle => TextStyleSupport.bold;@overrideStringtext(Stringsrc) {returnsrc.substring(start+2, end-2);  }}复制代码

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

if(_scanner.scan(RegExp(r'\*\*.*?\*\*'))) {intstartIndex = _scanner.lastMatch.start;intendIndex = _scanner.lastMatch.end;  _spans.add(BoldSpanBean(startIndex, endIndex));}复制代码

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

2.文字倾斜处理

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

// 加粗匹配if(_scanner.scan(RegExp(r'\*\*.*?\*\*'))) {intstartIndex = _scanner.lastMatch.start;intendIndex = _scanner.lastMatch.end;  _spans.add(BoldSpanBean(startIndex, endIndex));}// 倾斜匹配if(_scanner.scan(RegExp(r'\*.*?\*'))) {intstartIndex = _scanner.lastMatch.start;intendIndex = _scanner.lastMatch.end;  _spans.add(LeanSpanBean(startIndex, endIndex));}复制代码

//加粗规则: *data*classLeanSpanBeanextendsSpanBean{  LeanSpanBean(intstart,intend) :super(start, end);@overrideTextStylegetstyle => TextStyleSupport.lean;@overrideStringtext(Stringsrc) {returnsrc.substring(start+1, end-1);  }}复制代码

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,635评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,628评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,971评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,986评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,006评论 6 394
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,784评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,475评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,364评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,860评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,008评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,152评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,829评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,490评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,035评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,428评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,127评论 2 356

推荐阅读更多精彩内容