帶你深刻理解 Flutter 中的字體「冷」知識

本篇將帶你深刻理解 Flutter 開發過程當中關於字體和文本渲染的「冷」知識,幫助你理解和增長關於 Flutter 中字體繪製的「無用」知識點。git

畢竟此類相關的內容太少了github

首先從一個簡單的文本顯示開始,以下代碼所示,運行後能夠看到界面內出現了一個 H 字母,它的 fontSize100Text 被放在一個高度爲 200Container 中,而後若是這時候有人問你: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,
                )
              ],
            ),
          )

        ),
      ),
    );
  }
複製代碼

1、TextStyle

以下代碼所示,爲了解答這個問題,首先咱們給 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 的大小;

以下圖所示,藍色區域和紅色區域的對比就是 heightnull1 的對比高度。ui

另外上圖的 BaseLine 也解釋了:爲何 fontSize 爲 100 的 H 字母,不是充滿高度爲 100 的藍色區域。spa

根據上圖的示意效果,在 height 爲 1 的紅色區域內,H 字母也應該是顯示在基線之上,而基線的底部區域是爲了如 g 和 j 等字母預留,因此以下圖所示,在 Text 內加入 g 字母並打開 Flutter 調試的文本基線顯示,由 Flutter 渲染的綠色基線也能夠看到符合咱們預期的效果。

忘記截圖由 g 的了,腦補吧。

接着以下代碼所示,當咱們把 height 設置爲 2 ,而且把上層的高度爲 200Container 添加一個紫色背景,結果以下圖所示,能夠看到藍色塊恰好充滿紫色方塊,由於 fontSize100 的文本在 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 是往下偏移的,爲何這樣偏移在後面會介紹,還會有新的對比。

最後以下圖所示,是官方提供的在不一樣 TextStyleheight 參數下, Text 所佔高度的對比狀況。

2、StrutStyle

那再回顧下前面所說的默認字體的量度,這個默認字體的量度又是如何組成的呢?這就不得不說到 StrutStyle

以下代碼所示,在以前的代碼中添加 StrutStyle

  • 設置了 forceStrutHeight 爲 true ,這是由於只有 forceStrutHeight 才能強制重置 Textheight 屬性;
  • 設置了StrutStyleheight 設置爲 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,
                )
              ],
            ),
          )

        ),
      ),
    );
  }
複製代碼

效果以下圖所示,雖然 TextStyleheight2 ,可是顯示出現是以 StrutStyleheight1 的效果爲準。

而後查看文檔對於 StrutStyleheight 的描述,能夠看到:height 的效果依然是 fontSize 的倍數,可是不一樣的是這裏的對 fontSize 進行了補充說明 : ascent + descent = fontSize,其中:

  • ascent 表明的是基線上方部分;

  • descent 表明的是基線的半部分

  • 其組合效果以下圖所示:

Flutter 中 ascentdescent 是不能用代碼單獨設置。

除此以外,StrutStylefontSizeTextStylefontSize 做用並不同:當咱們把 StrutStylefontSize 設置爲 50 ,而 TextStylefontSize 依然是 100 時,以下圖所示,能夠看到黑色的字體大小沒有發生變化,而藍色部分的大小變爲了 50 的大小。

有人就要說那 StrutStyle 這樣的 fontSize 有什麼用?

這時候,若是在上面條件不變的狀況下,把 Text 中的文本變成 "Hg\nHg" 這樣的兩行文本,能夠看到換行後的文本重疊在了一塊兒,因此 StrutStylefontSize 也是會影響行高

另外,在 StrutStyle 中還有另一個參數也會影響行高,那就是 leading

以下圖所示,加上了 leading 後纔是 Flutter 中對字體行高徹底的控制組合,leading 默認爲 null ,同時它的效果也是 fontSize 的倍數,而且分佈是上下均分。

因此以下代碼所示,當 StrutStylefontSize100height 爲 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 是根據 ascentdescent 的部分放大,明顯 ascentdescent 大得多,因此前面的 TextStyleheight 爲 2 時,充滿後總體往下偏移。

3、backgroundColor

那麼到這裏應該對於 Flutter 中關於文本大小、度量和行高等有了基本的認知,接着再介紹一個屬性:TextStylebackgroundColor

介紹這個屬性是爲了和前面的內容產生一個對比,而且解除一些誤解。

以下代碼所示,能夠看到 StrutStylefontSize100height1,按照前面的介紹,藍色的區域大小應該是和紅色小方塊同樣大。

而後咱們設置了 TextStylebackgroundColor 爲具備透明度的綠色,結果以下圖所示,能夠看到 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,咱們能夠改變 TextStyleheight 或者 StrutStyle 來改變行高效果,可是本質上的 fontSize 其實並無變。

若是把輸入內容換成 "H\ng" ,以下圖所示能夠看到更有意思的效果。

4、TextBaseline

最後再介紹一個屬性 :TextStyleTextBaseline,由於這個屬性一直讓人產生「誤解」。

關於 TextBaseline 有兩個屬性,分別是 alphabeticideographic ,爲了更方便解釋他們的效果,以下代碼所示,咱們經過 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 展現不出來。

舉個典型的例子,以下代碼所示,雖然在 RowText 上都是用了 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 中的字體相關的「冷」知識介紹完了,不知道你「無用」的知識有沒有增多呢?

相關文章
相關標籤/搜索