本篇將帶你深刻理解 Flutter 開發過程當中關於字體和文本渲染的「冷」知識,幫助你理解和增長關於 Flutter 中字體繪製的「無用」知識點。git
畢竟此類相關的內容太少了github
首先從一個簡單的文本顯示開始,以下代碼所示,運行後能夠看到界面內出現了一個 H 字母,它的 fontSize
是 100,Text
被放在一個高度爲 200 的 Container
中,而後若是這時候有人問你:Text
顯示 H 字母須要佔據多大的高度,你知道嗎?canvas
@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, ) ], ), ) ), ), ); } 複製代碼
以下代碼所示,爲了解答這個問題,首先咱們給 Text
所在的 Container
增長了一個藍色背景,並增長一個 100 * 100
大小的紅色小方塊作對比。bash
@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
的高度,它的大小又是如何組成的呢?markdown
事實上,前面的藍色區域是字體的行高,也就是 line height ,關於這個行高,首先須要解釋的就是 TextStyle
中的 height
參數。ide
默認狀況下 height
參數是 null
,當咱們把它設置爲 1
以後,以下圖所示,能夠看到藍色區域的高度和紅色小方塊對齊,變成了 100 的高度,也就是行高變成了 100 ,而 H 字母完整的顯示在藍色區域內。oop
那 height
是什麼呢?根據文檔可知,首先 TextStyle
中的 height
參數值在設置後,其效果值是 fontSize
的倍數:字體
height
爲空時,行高默認是使用字體的量度(這個量度後面會有解釋);height
不是空時,行高爲 height
* fontSize
的大小;以下圖所示,藍色區域和紅色區域的對比就是 height
爲 null
和 1
的對比高度。ui
另外上圖的 BaseLine
也解釋了:爲何 fontSize
爲 100 的 H 字母,不是充滿高度爲 100 的藍色區域。spa
根據上圖的示意效果,在 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
:
forceStrutHeight
爲 true ,這是由於只有 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
比descent
大得多,因此前面的TextStyle
的height
爲 2 時,充滿後總體往下偏移。
那麼到這裏應該對於 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"
,以下圖所示能夠看到更有意思的效果。
最後再介紹一個屬性 :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 中的字體相關的「冷」知識介紹完了,不知道你「無用」的知識有沒有增多呢?