Flutter 文本解释 5 | RichText 富文本使用(上)
零、前言
通过前四篇,我们已经了解了 Text 的源码实现和基本使用方式。其本质是使用了 RichText
进行构建的,也就是说认识了 Text
就等价于认识了 RichText
。通过 Text.rich
我们也可以方便地构建富文本组件,在第三篇中介绍了一下 Text.rich
,本篇就来详细地介绍一下富文本的使用。本篇和之前的几篇关系不大,可单独食用。
- 《Flutter 组件 | Text 文本解读 (一) 》
- 《Flutter 组件 | Text 文本解读 (二) 》
- 《Flutter 组件 | Text 文本解读 (三) 》
- 《Flutter 组件 | Text 文本解读 (四) 》
一、认识 InlineSpan
1. Text.rich 做了什么
Text 组件内部有一个 InlineSpan
类型的 textSpan
成员。它只能通过 Text.rich
构造进行赋值。使用 Text 普通构造时,该成员为 null。
---->[Text 源码]----
final InlineSpan textSpan;
const Text.rich(
this.textSpan, {
//... 略
该成员如果非空,会用于 Text#build
时,作为 RichText
中 TextSpan
的 children
,实现富文本。
2. InlineSpan 是什么
InlineSpan
是一个抽象类,所以我们需要使用其子类,实现类有 TextSpan
和 WidgetSpan
两个,分别用于实现多样文本样式
和文本中添加组件
。
如下面的的需求,我们需要使用 TextSpan
,在一个 TextSpan
中可以传入 List<InlineSpan>
,从而可以得到一个树状的结构。实现代码如下:
class HomePage extends StatelessWidget {
final TextStyle linkStyle = const TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline,
decorationColor: Colors.blue);
final TextStyle defaultStyle = const TextStyle(
color: Colors.black);
@override
Widget build(BuildContext context) {
InlineSpan span = TextSpan(children: [
TextSpan(text: '我已同意 ', style: defaultStyle),
TextSpan(text: '服务条款', style: linkStyle),
TextSpan(text: ' 和 ', style: defaultStyle),
TextSpan(text: '隐私政策', style: linkStyle),
TextSpan(text: ' 。', style: defaultStyle),
]);
return Text.rich(span);
}
}
3.InlineSpan 中的点击事件
InlineSpan
中有一个 recognizer
成员,类型为 GestureRecognizer
。它是一个抽象类,有着很多的实现类,我们可以根据不同的手势选择不同的实现类。
其中点击事件可以使用 TapGestureRecognizer
,它可以监听到 按下
、点击
、抬起
、取消
等事件。这样我们就可以对一个 InlineSpan
进行点击监听。效果如下:
这样就可以在点击时执行方法,跳转到对应的条款界面。要注意的是 GestureRecognizer
对象需要做 dispose
操作,代码使用如下:
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final TextStyle linkStyle = const TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline,
decorationColor: Colors.blue);
final TextStyle defaultStyle = const TextStyle(color: Colors.black);
TapGestureRecognizer _tapServer;
TapGestureRecognizer _tapPolicy;
@override
void initState() {
super.initState();
_tapServer= TapGestureRecognizer()..onTap=(){
print('点击 服务条款');
};
_tapPolicy= TapGestureRecognizer()..onTap=(){
print('点击 隐私政策');
};
}
@override
void dispose() {
_tapServer.dispose(); // 销毁对象
_tapPolicy.dispose(); // 销毁对象
super.dispose();
}
@override
Widget build(BuildContext context) {
InlineSpan span = TextSpan(children: [
TextSpan(text: '我已同意 ', style: defaultStyle),
TextSpan(text: '服务条款', style: linkStyle, recognizer: _tapServer),
TextSpan(text: ' 和 ', style: defaultStyle, ),
TextSpan(text: '隐私政策', style: linkStyle,recognizer: _tapPolicy),
TextSpan(text: ' 。', style: defaultStyle),
]);
return Text.rich(span);
}
}
4. WidgetSpan
通过 WidgetSpan
可以在文字中添加任何 Widget
,比如下面的图片。
@override
Widget build(BuildContext context) {
final Image image = Image.asset(
'assets/images/icon_head.webp',
width: 20,
height: 20,
);
InlineSpan span = TextSpan(children: [
WidgetSpan(child: image,),
TextSpan(text: ' 我已同意 ', style: defaultStyle),
TextSpan(text: '服务条款', style: linkStyle, recognizer: _tapServer),
TextSpan(text: ' 和 ', style: defaultStyle,),
TextSpan(text: '隐私政策', style: linkStyle, recognizer: _tapPolicy),
TextSpan(text: ' 。', style: defaultStyle),
]);
return Text.rich(span);
}
WidgetSpan
中可以设置 PlaceholderAlignment
对齐方式和 基线 TextBaseline
,其中对齐方式含 baseline
字样的,必须设置 TextBaseline
。六种对齐方式如下:
到这里,我们就简单地认识完了 InlineSpan
实现富文本的用法。
二、局部文字高亮
文字很少的时候我们用 InlineSpan
来一个个拼,但是对于大段文本的展示,自己拼装是不切实际的。这时候就需要按照某些规则,进行字符串的解析,然后统一生成 InlineSpan
。
1.字符串解析
我们先看下面的一段文字,其中有些内容是高亮显示的。可以定义一个规则,然后进行解析。
虽然我们可以自己定义规则,但是在 .md
中已有了规则,最好还是使用共同遵守的规则,如下。
首先我们需要找到被反引号包住的字符串,下面通过写一个 StringParser
类负责文本的解析。其中主要通过 StringScanner
对文本进行扫描,通过下面的正则可以将被包裹的文字位置解析出来。
class StringParser {
final String content;
StringParser({this.content});
StringScanner _scanner;
parser() {
_scanner = StringScanner(content);
parseContent();
}
void parseContent() {
while (!_scanner.isDone) {
if (_scanner.scan(RegExp('`.*?`'))) {
print(content.substring(_scanner.lastMatch.start+1,_scanner.lastMatch.end-1));
}
if (!_scanner.isDone) {
_scanner.position++;
}
}
}
}
2.定义高亮数据类型
我们通过有个 SpanBean
来存储 InlineSpan
需要的信息。通过 TextStyleSupport
指定高亮支持的文字样式类型。
class SpanBean {
SpanBean(this.start, this.end);
final int start;
final int end;
String text(String src) {
return src.substring(start+1, end-1);
}
TextStyle get style => TextStyleSupport.dotWrapStyle;
}
class TextStyleSupport{
static const defaultStyle = TextStyle(color: Colors.black,fontSize: 14);
static const dotWrapStyle = TextStyle(color: Colors.purple,fontSize: 14);
}
这样在 parseContent
中,就可以将解析出的有用信息保存到 SpanBean
中,并用集合进行维护。
List<SpanBean> _spans = [];
void parseContent() {
while (!_scanner.isDone) {
if (_scanner.scan(RegExp('`.*?`'))) {
int startIndex = _scanner.lastMatch.start ;
int endIndex = _scanner.lastMatch.end ;
_spans.add(SpanBean(startIndex, endIndex));
}
if (!_scanner.isDone) {
_scanner.position++;
}
}
}
3.通过 SpanBean 集合生成 InlineSpan
遍历 SpanBean 集合,将其之外的只为默认样式,其区间内的文字设置为指定样式。
InlineSpan parser() {
_scanner = StringScanner(content);
parseContent();
final List<TextSpan> spans = <TextSpan>[];
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)));
currentPosition = span.end;
}
if (currentPosition != content.length)
spans.add(TextSpan(text: content.substring(currentPosition, content.length)));
return TextSpan(style: TextStyleSupport.defaultStyle, children: spans);
}
4.使用
这样通过 StringParser#parser
就可以获取到 InlineSpan
,进行显示。这样我们就完成了一个简易的包裹高亮的需求。使用起来也非常方便,有时只是需要高亮一些内容,没有必要用到 markdown
解析的库,这里也就百来行代码。通过自己写出来,可以对内部有更深的了解,想修改样式什么的,也就游刃有余。
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
InlineSpan span;
final String content = """
可能说起 Flutter 绘制,大家第一反应就是用 `CustomPaint` 组件,自定义 `CustomPainter` 对象来画。Flutter 中所有可以看得到的组件,比如 Text、Image、Switch、Slider 等等,追其根源都是`画出来`的,但通过查看源码可以发现,Flutter 中绝大多数组件并不是使用 `CustomPaint` 组件来画的,其实 `CustomPaint` 组件是对框架底层绘制的一层封装。这个系列便是对 Flutter 绘制的探索,通过`测试`、`调试`及`源码分析`来给出一些在绘制时`被忽略`或`从未知晓`的东西,而有些要点如果被忽略,就很可能出现问题。
""";
StringParser parser;
@override
void initState() {
super.initState();
parser = StringParser(content: content);
span = parser.parser();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(20.0),
child: Text.rich(span),
);
}
}
这样一个简单的包裹高亮文本就实现了,如果想要打造自己的解析规则,也可以自己定制,这就是创造者的*。本篇就介绍这些,在之后的文章中,将会继续拓展文本解析,比如链接的解析、Markdown 的一些基本语法等。这样 Text 就不仅是文本那么简单,还涉及着字符串的解析、正则的使用等更高阶的技能。
当我们掌握了这些能力,再回看代码的高亮显示的实现,也就会驾轻就熟
。换到另一个平台上,web、Android等,我们只需知道解析的方法,整个流程都是类似的,这就是经验和能力,和绘制一样,这些能力并不会随着框架的没落而退散,你会了,它就是你的。本文就到这里,谢谢观看~
@张风捷特烈 2021.01.20 未允禁转
我的公众号:编程之王
联系我--邮箱:1981462002@qq.com -- 微信:zdl1994328
~ END ~
推荐阅读
-
如何使用问卷星创建保险需求分析问卷
-
使用 Docker 部署 SurveyKing 勘测系统
-
简氏时间 - 用户使用情况调查报告
-
[学习笔记] - mooc - 教学研究的数据处理与工具应用(问卷调查+问卷之星的使用+SPSS的下载、安装、使用与入门) - 华南师范大学
-
问卷明星复制内容法(使用官方功能导出为 word 格式)
-
利用 Google & Tampermonkey & Modify Headers 实现问卷星调查问卷自动填写提交--操作与使用
-
使用 Python 自动填写问卷星级(pyppeteer 反爬虫版本)
-
使用 Python 自动完成问卷星(超级详细!!!)。
-
AMD 锐龙 7 8700F评测:游戏、人工智能全面战胜 i5-14400F!
-
全面解释 python @property 的用法和含义