本篇將帶你深入理解 Flutter 開發(fā)過程中關(guān)于字體和文本渲染的“冷”知識(shí)提茁,幫助你理解和增加關(guān)于 Flutter 中字體繪制的“無用”知識(shí)點(diǎn)傍念。
畢竟此類相關(guān)的內(nèi)容太少了
首先從一個(gè)簡單的文本顯示開始墓怀,如下代碼所示,運(yùn)行后可以看到界面內(nèi)出現(xiàn)了一個(gè) H 字母躺翻,它的 fontSize
是 100等舔,Text
被放在一個(gè)高度為 200 的 Container
中,然后如果這時(shí)候有人問你:Text
顯示 H 字母需要占據(jù)多大的高度翁巍,你知道嗎驴一?
@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
如下代碼所示灶壶,為了解答這個(gè)問題肝断,首先我們給 Text
所在的 Container
增加了一個(gè)藍(lán)色背景,并增加一個(gè) 100 * 100
大小的紅色小方塊做對(duì)比。
@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,
)
],
),
)
),
),
);
}
結(jié)果如下圖所示胸懈,可以看到 H 字母的上下有著一定的 padding
區(qū)域担扑,藍(lán)色Container
的大小明顯超過了 100 ,但是黑色的 H 字母本身并沒有超過紅色小方塊趣钱,那藍(lán)色區(qū)域的高度是不是 Text
的高度涌献,它的大小又是如何組成的呢?
事實(shí)上首有,前面的藍(lán)色區(qū)域是字體的行高燕垃,也就是 line height ,關(guān)于這個(gè)行高井联,首先需要解釋的就是 TextStyle
中的 height
參數(shù)卜壕。
默認(rèn)情況下 height
參數(shù)是 null
,當(dāng)我們把它設(shè)置為 1
之后低矮,如下圖所示印叁,可以看到藍(lán)色區(qū)域的高度和紅色小方塊對(duì)齊,變成了 100 的高度军掂,也就是行高變成了 100 轮蜕,而 H 字母完整的顯示在藍(lán)色區(qū)域內(nèi)。
那 height
是什么呢蝗锥?根據(jù)文檔可知跃洛,首先 TextStyle
中的 height
參數(shù)值在設(shè)置后,其效果值是 fontSize
的倍數(shù):
- 當(dāng)
height
為空時(shí)终议,行高默認(rèn)是使用字體的量度(這個(gè)量度后面會(huì)有解釋)汇竭; - 當(dāng)
height
不是空時(shí),行高為height
*fontSize
的大醒ㄕ拧细燎;
如下圖所示,藍(lán)色區(qū)域和紅色區(qū)域的對(duì)比就是 height
為 null
和 1
的對(duì)比高度皂甘。
另外上圖的 BaseLine
也解釋了:為什么 fontSize
為 100 的 H 字母玻驻,不是充滿高度為 100 的藍(lán)色區(qū)域。
根據(jù)上圖的示意效果偿枕,在 height
為 1 的紅色區(qū)域內(nèi)璧瞬,H 字母也應(yīng)該是顯示在基線之上,而基線的底部區(qū)域是為了如 g 和 j 等字母預(yù)留渐夸,所以如下圖所示嗤锉,在 Text
內(nèi)加入 g 字母并打開 Flutter 調(diào)試的文本基線顯示,由 Flutter 渲染的綠色基線也可以看到符合我們預(yù)期的效果墓塌。
忘記截圖由 g 的了瘟忱,腦補(bǔ)吧奥额。
接著如下代碼所示,當(dāng)我們把 height
設(shè)置為 2
酷誓,并且把上層的高度為 200 的 Container
添加一個(gè)紫色背景披坏,結(jié)果如下圖所示态坦,可以看到藍(lán)色塊剛好充滿紫色方塊盐数,因?yàn)?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
是往下偏移的伞梯,為什么這樣偏移在后面會(huì)介紹玫氢,還會(huì)有新的對(duì)比。
最后如下圖所示谜诫,是官方提供的在不同 TextStyle
的 height
參數(shù)下漾峡, Text
所占高度的對(duì)比情況。
二喻旷、StrutStyle
那再回顧下前面所說的默認(rèn)字體的量度生逸,這個(gè)默認(rèn)字體的量度又是如何組成的呢?這就不得不說到 StrutStyle
且预。
如下代碼所示槽袄,在之前的代碼中添加 StrutStyle
:
- 設(shè)置了
forceStrutHeight
為 true ,這是因?yàn)橹挥?forceStrutHeight
才能強(qiáng)制重置Text
的height
屬性锋谐; - 設(shè)置了
StrutStyle
的height
設(shè)置為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
,但是顯示出現(xiàn)是以 StrutStyle
中 height
為 1
的效果為準(zhǔn)乾戏。
然后查看文檔對(duì)于 StrutStyle
中 height
的描述,可以看到:height
的效果依然是 fontSize
的倍數(shù)三热,但是不同的是這里的對(duì) fontSize
進(jìn)行了補(bǔ)充說明 : ascent + descent = fontSize
鼓择,其中:
ascent
代表的是基線上方部分;descent
代表的是基線的半部分其組合效果如下圖所示:
Flutter 中
ascent
和descent
是不能用代碼單獨(dú)設(shè)置就漾。
除此之外呐能,StrutStyle
的 fontSize
和 TextStyle
的 fontSize
作用并不一樣:當(dāng)我們把 StrutStyle
的 fontSize
設(shè)置為 50 ,而 TextStyle
的 fontSize
依然是 100 時(shí)从藤,如下圖所示令杈,可以看到黑色的字體大小沒有發(fā)生變化,而藍(lán)色部分的大小變?yōu)榱?50 的大小迫靖。
有人就要說那 StrutStyle
這樣的 fontSize
有什么用拇泛?
這時(shí)候,如果在上面條件不變的情況下悯搔,把 Text
中的文本變成 "Hg\nHg"
這樣的兩行文本骑丸,可以看到換行后的文本重疊在了一起,所以 StrutStyle
的 fontSize
也是會(huì)影響行高。
另外通危,在 StrutStyle
中還有另外一個(gè)參數(shù)也會(huì)影響行高铸豁,那就是 leading
。
如下圖所示菊碟,加上了 leading
后才是 Flutter 中對(duì)字體行高完全的控制組合节芥,leading
默認(rèn)為 null
,同時(shí)它的效果也是 fontSize
的倍數(shù)逆害,并且分布是上下均分头镊。
所以如下代碼所示,當(dāng) StrutStyle
的 fontSize
為 100 魄幕,height
為 1相艇,leading
為 1 時(shí),可以看到 leading
的大小讓藍(lán)色區(qū)域變?yōu)榱?200纯陨,從而 和紫色區(qū)域高度又重疊了坛芽,不同的對(duì)比之前的 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,
)
],
),
)
),
),
);
}
因?yàn)?
leading
是上下均分的翼抠,而height
是根據(jù)ascent
和descent
的部分放大咙轩,明顯ascent
比descent
大得多,所以前面的TextStyle
的height
為 2 時(shí)机久,充滿后整體往下偏移臭墨。
三、backgroundColor
那么到這里應(yīng)該對(duì)于 Flutter 中關(guān)于文本大小膘盖、度量和行高等有了基本的認(rèn)知胧弛,接著再介紹一個(gè)屬性:TextStyle
的 backgroundColor
。
介紹這個(gè)屬性是為了和前面的內(nèi)容產(chǎn)生一個(gè)對(duì)比侠畔,并且解除一些誤解结缚。
如下代碼所示,可以看到 StrutStyle
的 fontSize
為 100 软棺,height
為 1
红竭,按照前面的介紹,藍(lán)色的區(qū)域大小應(yīng)該是和紅色小方塊一樣大喘落。
然后我們?cè)O(shè)置了 TextStyle
的 backgroundColor
為具有透明度的綠色茵宪,結(jié)果如下圖所示,可以看到 backgroundColor
的區(qū)域超過了 StrutStyle
瘦棋,顯示為默認(rèn)情況下字體的度量稀火。
@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,
)
],
),
)
),
),
);
}
這是不是很有意思,事實(shí)上也可以反應(yīng)出赌朋,字體的度量其實(shí)一直都是默認(rèn)的 ascent + descent = fontSize
凰狞,我們可以改變 TextStyle
的 height
或者 StrutStyle
來改變行高效果篇裁,但是本質(zhì)上的 fontSize
其實(shí)并沒有變。
如果把輸入內(nèi)容換成 "H\ng"
赡若,如下圖所示可以看到更有意思的效果达布。
四、TextBaseline
最后再介紹一個(gè)屬性 :TextStyle
的 TextBaseline
,因?yàn)檫@個(gè)屬性一直讓人產(chǎn)生“誤解”逾冬。
關(guān)于 TextBaseline
有兩個(gè)屬性黍聂,分別是 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;
}
如下圖所示霸株,藍(lán)色的線就是 baseLine,從效果可以直觀看到不同 baseLine 下對(duì)齊的位置應(yīng)該在哪里集乔。
但是事實(shí)上 baseLine 的作用并不會(huì)直接影響 TextStyle
中文本的對(duì)齊方式去件,F(xiàn)lutter 中默認(rèn)顯示的文本只會(huì)通過 TextBaseline.alphabetic
對(duì)齊的,如下圖所示官方人員也對(duì)這個(gè)問題有過描述 #47512扰路。
這也是為什么要用
CustomPaint
展示的原因尤溜,因?yàn)橛媚J(rèn)Text
展示不出來。
舉個(gè)典型的例子汗唱,如下代碼所示宫莱,雖然在 Row
和 Text
上都是用了 ideographic
,但是其實(shí)并沒有達(dá)到我們想要的效果哩罪。
@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,
)),
])),
),
);
}
關(guān)鍵就算
Row
設(shè)置了center
授霸,這段文本看起來還是不是特別“對(duì)齊”。
自從际插,關(guān)于 Flutter 中的字體相關(guān)的“冷”知識(shí)介紹完了碘耳,不知道你“無用”的知識(shí)有沒有增多呢?