带你深入理解 Flutter 中的字体 “冷” 知识
source link: https://mp.weixin.qq.com/s/BPzhALUcXkMAV3II4Y8BrA
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
本篇将带你深入理解 Flutter 开发过程中关于字体和文本渲染的“冷”知识,帮助你理解和增加关于 Flutter 中字体绘制的“无用”知识点。
毕竟此类相关的内容太少了
首先从一个简单的文本显示开始,如下代码所示,运行后可以看到界面内出现了一个 H
字母,它的 fontSize
是 100
, Text
被放在一个高度为 200
的 Container
中,然后如果这时候有人问你:
Text
显示 H
字母需要占据多大的高度,你知道吗?
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Container(
color: Colors.lime,
alignment: Alignment.center,
child: Container(
alignment: Alignment.center,
child: Container(
height: 200,
alignment: Alignment.center,
child: new Row(
children: <Widget>[
Container(
child: new Text(
"H",
style: TextStyle(
fontSize: 100,
),
),
),
Container(
height: 100,
width: 100,
color: Colors.red,
)
],
),
)
),
),
);
}
一、TextStyle
如下代码所示,为了解答这个问题,首先我们给 Text
所在的 Container
增加了一个蓝色背景,并增加一个 100 * 100
大小的红色小方块做对比。
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Container(
color: Colors.lime,
alignment: Alignment.center,
child: Container(
alignment: Alignment.center,
child: Container(
height: 200,
alignment: Alignment.center,
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
color: Colors.blue,
child: new Text(
"H",
style: TextStyle(
fontSize: 100,
),
),
),
Container(
height: 100,
width: 100,
color: Colors.red,
)
],
),
)
),
),
);
}
结果如下图所示,可以看到 H
字母的上下有着一定的 padding
区域,蓝色 Container
的大小明显超过了 100
,但是黑色的 H
字母本身并没有超过红色小方块,那蓝色区域的高度是不是 Text
的高度,它的大小又是如何组成的呢?
事实上,前面的蓝色区域是字体的行高,也就是 line height,关于这个行高,首先需要解释的就是 TextStyle
中的 height
参数。
默认情况下 height
参数是 null
,当我们把它设置为
1
之后,如下图所示,可以看到蓝色区域的高度和红色小方块对齐,变成了 100
的高度,也就是行高变成了 100
,而 H
字母完整的显示在蓝色区域内。
那 height
是什么呢?根据文档可知,首先 TextStyle
中的 height
参数值在设置后,其效果值是 fontSize
的倍数:
-
当
height
为空时,行高默认是使用字体的 量度 (这个 量度 后面会有解释); -
height height fontSize
如下图所示,蓝色区域和红色区域的对比就是 height
为 null
和 1
的对比高度。
另外上图的 BaseLine
也解释了:为什么 fontSize
为 100 的 H
字母,不是充满高度为 100 的蓝色区域。
根据上图的示意效果,在 height
为 1 的红色区域内, H
字母也应该是显示在基线之上,而基线的底部区域是为了如 g 和 j 等字母预留,所以如下图所示,在 Text
内加入 g 字母并打开 Flutter 调试的文本基线显示,由 Flutter 渲染的绿色基线也可以看到符合我们预期的效果。
忘记截图由 g 的了,脑补吧。
接着如下代码所示,当我们把 height
设置为
2
,并且把上层的高度为 200
的 Container
添加一个紫色背景,结果如下图所示,可以看到蓝色块刚好充满紫色方块,因为 fontSize
为 100
的文本在 x2
之后恰好高度就是 200
。
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Container(
color: Colors.lime,
alignment: Alignment.center,
child: Container(
alignment: Alignment.center,
child: Container(
height: 200,
color: Colors.purple,
alignment: Alignment.center,
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
color: Colors.blue,
child: new Text(
"Hg",
style: TextStyle(
fontSize: 100,
height: 2,
),
),
),
Container(
height: 100,
width: 100,
color: Colors.red,
)
],
),
)
),
),
);
}
不过这里的
Hg
是往下偏移的,为什么这样偏移在后面会介绍,还会
有 新的对比。
最后如下图所示,是官方提供的在不同 TextStyle
的 height
参数下, Text
所占高度的对比情况。
二、StrutStyle
那再回顾下前面所说的默认字体的 量度
,这个默认字体的 量度
又是如何组成的呢?这就不得不说到 StrutStyle
。
如下代码所示,在之前的代码中添加 StrutStyle
:
-
forceStrutHeight forceStrutHeight Text height
-
设置了
StrutStyle
的height
设置为1
,这样TextStyle
中的height
等于2
就没有了效果。
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Container(
color: Colors.lime,
alignment: Alignment.center,
child: Container(
alignment: Alignment.center,
child: Container(
height: 200,
color: Colors.purple,
alignment: Alignment.center,
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
color: Colors.blue,
child: new Text(
"Hg",
style: TextStyle(
fontSize: 100,
height: 2,
),
strutStyle: StrutStyle(
forceStrutHeight: true,
fontSize: 100,
height: 1
),
),
),
Container(
height: 100,
width: 100,
color: Colors.red,
)
],
),
)
),
),
);
}
效果如下图所示,虽然 TextStyle
的 height
是
2
,但是显示出现是以 StrutStyle
中 height
为
1
的效果为准。
然后查看文档对于 StrutStyle
中 height
的描述,可以看到: height
的效果依然是 fontSize
的倍数,但是不同的是这里的对 fontSize
进行了补充说明 : ascent + descent = fontSize
,其中:
-
ascent
代表的是基线上方部分; -
descent
代表的是基线的下半部 -
其组合效果如下图所示:
Flutter 中 ascent
和 descent
是不能用代码单独设置。
除此之外,
StrutStyle
的 fontSize
和 TextStyle
的 fontSize
作用并不一样
:当我们把 StrutStyle
的 fontSize
设置为 50
,而 TextStyle
的 fontSize
依然是 100
时,如下图所示,可以看到黑色的字体大小没有发生变化,而蓝色部分的大小变为了 50
的大小。
有人就要说那 StrutStyle
这样的 fontSize
有什么用?
这时候,如果在上面条件不变的情况下,把 Text
中的文本变成 "Hg\nHg"
这样的两行文本,可以看到换行后的文本重叠在了一起,
所以 StrutStyle
的 fontSize
也是会影响行高
。
另外,在 StrutStyle
中还有另外一个参数也会影响行高,那就是 leading
。
如下图所示,加上了 leading
后才是 Flutter 中对字体行高完全的控制组合, leading
默认为 null
,同时它的效果也是 fontSize
的倍数,并且分布是上下均分。
所以如下代码所示,当 StrutStyle
的 fontSize
为 100
, height
为 1, leading
为 1 时,可以看到 leading
的大小让蓝色区域变为了 200
,从而 和紫色区域高度又重叠了,不同的对比之前的 Hg
在这次充满显示是居中。
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Container(
color: Colors.lime,
alignment: Alignment.center,
child: Container(
alignment: Alignment.center,
child: Container(
height: 200,
color: Colors.purple,
alignment: Alignment.center,
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
color: Colors.blue,
child: new Text(
"Hg",
style: TextStyle(
fontSize: 100,
height: 2,
),
strutStyle: StrutStyle(
forceStrutHeight: true,
fontSize: 100,
height: 1,
leading: 1
),
),
),
Container(
height: 100,
width: 100,
color: Colors.red,
)
],
),
)
),
),
);
}
因为 leading
是上下均分的,而 height
是根据 ascent
和 descent
的部分放大,明显 ascent
比 ascent
大得多,所以前面的 TextStyle
的 height
为 2 时,充满后整体往下偏移。
三、backgroundColor
那么到这里应该对于 Flutter 中关于文本大小、度量和行高等有了基本的认知,接着再介绍一个属性: TextStyle
的 backgroundColor
。
介绍这个属性是为了和前面的内容产生一个对比,并且解除一些误解。
如下代码所示,可以看到 StrutStyle
的 fontSize
为 100
, height
为
1
,按照前面的介绍,蓝色的区域大小应该是和红色小方块一样大。
然后我们设置了 TextStyle
的 backgroundColor
为具有透明度的绿色,结果如下图所示,可以看到 backgroundColor
的区域超过了 StrutStyle
,显示为 默认情况下字体的度量
。
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Container(
color: Colors.lime,
alignment: Alignment.center,
child: Container(
alignment: Alignment.center,
child: Container(
height: 200,
color: Colors.purple,
alignment: Alignment.center,
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
color: Colors.blue,
child: new Text(
"Hg",
style: TextStyle(
fontSize: 100,
backgroundColor: Colors.green.withAlpha(180)
),
strutStyle: StrutStyle(
forceStrutHeight: true,
fontSize: 100,
height: 1,
),
),
),
Container(
height: 100,
width: 100,
color: Colors.red,
)
],
),
)
),
),
);
}
这是不是很有意思,事实上也可以反应出,字体的度量其实一直都是默认的 ascent + descent = fontSize
,我们可以改变 TextStyle
的 height
或者 StrutStyle
来改变行高效果,但是本质上的 fontSize
其实并没有变。
如果把输入内容换成 "H\ng"
,如下图所示可以看到更有意思的效果。
四、TextBaseline
最后再介绍一个属性 : TextStyle
的 TextBaseline
,因为这个属性一直让人产生“误解”。
关于 TextBaseline
有两个属性,分别是 alphabetic
和 ideographic
,为了更方便解释他们的效果,如下代码所示,我们通过 CustomPaint
把不同的基线位置绘制出来。
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Container(
color: Colors.lime,
alignment: Alignment.center,
child: Container(
alignment: Alignment.center,
child: Container(
height: 200,
width: 400,
color: Colors.purple,
child: CustomPaint(
painter: Text2Painter(),
),
)
),
),
);
}
class Text2Painter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
var baseLine = TextBaseline.alphabetic;
//var baseLine = TextBaseline.ideographic;
final textStyle =
TextStyle(color: Colors.white, fontSize: 100, textBaseline: baseLine);
final textSpan = TextSpan(
text: 'My文字',
style: textStyle,
);
final textPainter = TextPainter(
text: textSpan,
textDirection: TextDirection.ltr,
);
textPainter.layout(
minWidth: 0,
maxWidth: size.width,
);
final left = 0.0;
final top = 0.0;
final right = textPainter.width;
final bottom = textPainter.height;
final rect = Rect.fromLTRB(left, top, right, bottom);
final paint = Paint()
..color = Colors.red
..style = PaintingStyle.stroke
..strokeWidth = 1;
canvas.drawRect(rect, paint);
// draw the baseline
final distanceToBaseline =
textPainter.computeDistanceToActualBaseline(baseLine);
canvas.drawLine(
Offset(0, distanceToBaseline),
Offset(textPainter.width, distanceToBaseline),
paint..color = Colors.blue..strokeWidth = 5,
);
// draw the text
final offset = Offset(0, 0);
textPainter.paint(canvas, offset);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
如下图所示,蓝色的线就是 baseLine,从效果可以直观看到不同 baseLine 下对齐的位置应该在哪里。
但是事实上 baseLine 的作用并不会直接影响 TextStyle
中文本的对齐方式,Flutter 中默认显示的文本只会通过 TextBaseline.alphabetic
对齐的,如下图所示官方人员也对这个问题有过描述 #47512。
这也是为什么要用 CustomPaint
展示的原因,因为用默认 Text
展示不出来。
举个典型的例子,如下代码所示,虽然在 Row
和 Text
上都用了 ideographic
,但是其实并没有达到我们想要的效果。
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Container(
color: Colors.lime,
alignment: Alignment.center,
child: Container(
alignment: Alignment.center,
child: Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.ideographic,
mainAxisSize: MainAxisSize.max,
children: [
Text(
'我是中文',
style: TextStyle(
fontSize: 55,
textBaseline: TextBaseline.ideographic,
),
),
Spacer(),
Text('123y56',
style: TextStyle(
fontSize: 55,
textBaseline: TextBaseline.ideographic,
)),
])),
),
);
}
关键就算 Row
设置了 center
,这段文本看起来还是不是特别“对齐”。
自从,关于 Flutter 中的字体相关的“冷”知识介绍完了,不知道你“无用”的知识有没有增多呢?
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK