前言
这一篇文章我来带大家一起来阅读一下flutter框架中的文本组件Text相关源码,对于文本组件Text的使用大家应该是十分的熟悉,但其内部实现机制应该是很少有人去了解。当你了解完text的内部实现后,你会发现你完全可以通过自定义自己的Text来实现自己的文本内容和布局方式。话不多说,让我们一起进入flutter世界,一睹Text组件的真正容貌吧。
第一节:Text之TextSpan树的形成
阅读过Text源码的人都应该知道Text最终是由TextSpan组成的树形结构,以下为Text类中的两个数据参数:
final String data;
final InlineSpan textSpan;
以下为构建“data”数据参数的构造函数:
const Text(
this.data, {
Key key,
this.style,
this.strutStyle,
this.textAlign,
this.textDirection,
this.locale,
this.softWrap,
this.overflow,
this.textScaleFactor,
this.maxLines,
this.semanticsLabel,
this.textWidthBasis,
this.textHeightBehavior,
}) :
textSpan = null,
super(key: key);
上图代码中可以看到该构造函数只能传入“data”作为必要的参数,并且“textSpan = null”。
const Text.rich(
this.textSpan, {
Key key,
this.style,
this.strutStyle,
this.textAlign,
this.textDirection,
this.locale,
this.softWrap,
this.overflow,
this.textScaleFactor,
this.maxLines,
this.semanticsLabel,
this.textWidthBasis,
this.textHeightBehavior,
}) :
data = null,
super(key: key);
而上图代码中只能传入“textSpan”作为必要参数,并且“data = null”。
这里可以大概明了类Text只有两种构造方式:
- 将参数“data”作为构造源,此时忽略了"textSpan"参数。
- 将参数“textSpan"作为构造源,此时忽略了“data”参数。
我们再来看看Text类的“build“函数的主要逻辑:
@override
Widget build(BuildContext context) {
...
Widget result = RichText(
textAlign: textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start,
textDirection: textDirection, // RichText uses Directionality.of to obtain a default if this is null.
locale: locale, // RichText uses Localizations.localeOf to obtain a default if this is null
softWrap: softWrap ?? defaultTextStyle.softWrap,
overflow: overflow ?? defaultTextStyle.overflow,
textScaleFactor: textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
maxLines: maxLines ?? defaultTextStyle.maxLines,
strutStyle: strutStyle,
textWidthBasis: textWidthBasis ?? defaultTextStyle.textWidthBasis,
textHeightBehavior: textHeightBehavior ?? defaultTextStyle.textHeightBehavior ?? DefaultTextHeightBehavior.of(context),
text: TextSpan(
style: effectiveTextStyle,
text: data,
children: textSpan != null ? <InlineSpan>[textSpan] : null,
),
);
...
return result;
}
上图中我们主要观察"RichText"构造时传入的参数“text”,该text是一个"TextSpan"对象,该对象有两个主要的数据源:
- text - String类型,当类Text采用第一种传入“data”的构造方式时,该text才有值。
- children - List<InlineSpan>?类型,当类Text采用第二种传入"textSpan"的构造方式时,该children才不会为null。
从以上分析中得到这样的论述:
- 当以“data”构造Text时,树形中只会存在一个根TextSpan。
- 当以"textSpan"构造Text时,树形中会存在多个TextSpan子树结构。
我们具体看图:
1. 当以“data”构造Text时的树结构:
2. 当以"textSpan"构造Text时的树结构:
所以总结下来,不管Text是如何构建,内部总是会形成一棵TextSpan树。我们来看下面的例子:
Text.rich(
TextSpan(
text: "ABC\n",
children: [
TextSpan(
text: "DE\n",
children: [
TextSpan(
text: "F\n",
style: TextStyle(
fontSize: 12
)
)
]
),
TextSpan(
text: "HIJ\n",
style: TextStyle(
color: Colors.green
),
children: [
TextSpan(
text: "K\n",
style: TextStyle(
color: Colors.black
)
),
TextSpan(
text: "LMN\n",
)
]
)
]
),
style: TextStyle(
fontSize: 24,
color: Colors.red
)
)
显示效果如下:
代码结合上图图片显示可知,代码中的顺序为从上往下来显示每个TextSpan数据,这是符合用户角度的。
我们再来看该段代码演示的树形图:
从图形中的树状结构可知,每个TextSpan中都有“data”和"style"两个字段描述文本,其中“data”是文本字符内容,而“style”是用来修饰文本的显示风格的,我们称为文本样式。可以看到,如果子节点的文本样式为空或者样式中的某个属性没有,它就会继承父样式中的属性,这里采用的是就近原则,直到找到根Root的样式。当然如果根样式中依然未找到,就会使用默认值,像字体fontSize的默认值为14,颜色color的默认值为黑色等等。
那么,对于这种树形特性,其内部是如何工作的呢?我们知道Text类的构建函数“build“最终返回的是RichText类对象,我们再深入RichText类代码中一探究竟:
class RichText extends MultiChildRenderObjectWidget {
......
@override
RenderParagraph createRenderObject(BuildContext context) {
assert(textDirection != null || debugCheckHasDirectionality(context));
return RenderParagraph(text,
textAlign: textAlign,
textDirection: textDirection ?? Directionality.of(context),
softWrap: softWrap,
overflow: overflow,
textScaleFactor: textScaleFactor,
maxLines: maxLines,
strutStyle: strutStyle,
textWidthBasis: textWidthBasis,
textHeightBehavior: textHeightBehavior,
locale: locale ?? Localizations.localeOf(context, nullOk: true),
);
}
......
}
可以看到RichText类创建的渲染对象为RenderParagraph,并且将TextSpan的根节点“text”传入,其余参数我们暂且不讨论。继续看RenderParagraph类的关键代码:
@override
void performLayout() {
final Bo