帶你深入理解 Flutter 中的字體“冷”知識(shí)

本篇將帶你深入理解 Flutter 開發(fā)過程中關(guān)于字體和文本渲染的“冷”知識(shí)提茁,幫助你理解和增加關(guān)于 Flutter 中字體繪制的“無用”知識(shí)點(diǎn)傍念。

畢竟此類相關(guān)的內(nèi)容太少了

首先從一個(gè)簡單的文本顯示開始墓怀,如下代碼所示,運(yùn)行后可以看到界面內(nèi)出現(xiàn)了一個(gè) H 字母躺翻,它的 fontSize100等舔,Text 被放在一個(gè)高度為 200Container 中,然后如果這時(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,
                )
              ],
            ),
          )

        ),
      ),
    );
  }
image

一、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 的高度涌献,它的大小又是如何組成的呢?

image

事實(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)。

image

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ì)比就是 heightnull1 的對(duì)比高度皂甘。

image

另外上圖的 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ǔ)吧奥额。

image

接著如下代碼所示,當(dāng)我們把 height 設(shè)置為 2 酷誓,并且把上層的高度為 200Container 添加一個(gè)紫色背景披坏,結(jié)果如下圖所示态坦,可以看到藍(lán)色塊剛好充滿紫色方塊盐数,因?yàn)?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,
                )
              ],
            ),
          )

        ),
      ),
    );
  }
image

不過這里的 Hg 是往下偏移的伞梯,為什么這樣偏移在后面會(huì)介紹玫氢,還會(huì)有新的對(duì)比。

最后如下圖所示谜诫,是官方提供的在不同 TextStyleheight 參數(shù)下漾峡, Text 所占高度的對(duì)比情況。

image

二喻旷、StrutStyle

那再回顧下前面所說的默認(rèn)字體的量度生逸,這個(gè)默認(rèn)字體的量度又是如何組成的呢?這就不得不說到 StrutStyle 且预。

如下代碼所示槽袄,在之前的代碼中添加 StrutStyle

  • 設(shè)置了 forceStrutHeight 為 true ,這是因?yàn)橹挥?forceStrutHeight 才能強(qiáng)制重置 Textheight 屬性锋谐;
  • 設(shè)置了StrutStyleheight 設(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,
                )
              ],
            ),
          )

        ),
      ),
    );
  }

效果如下圖所示涮拗,雖然 TextStyleheight2 ,但是顯示出現(xiàn)是以 StrutStyleheight1 的效果為準(zhǔn)乾戏。

image

然后查看文檔對(duì)于 StrutStyleheight 的描述,可以看到:height 的效果依然是 fontSize 的倍數(shù)三热,但是不同的是這里的對(duì) fontSize 進(jìn)行了補(bǔ)充說明 : ascent + descent = fontSize鼓择,其中:

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

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

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

image

Flutter 中 ascentdescent 是不能用代碼單獨(dú)設(shè)置就漾。

除此之外呐能,StrutStylefontSizeTextStylefontSize 作用并不一樣:當(dāng)我們把 StrutStylefontSize 設(shè)置為 50 ,而 TextStylefontSize 依然是 100 時(shí)从藤,如下圖所示令杈,可以看到黑色的字體大小沒有發(fā)生變化,而藍(lán)色部分的大小變?yōu)榱?50 的大小迫靖。

image

有人就要說那 StrutStyle 這樣的 fontSize 有什么用拇泛?

這時(shí)候,如果在上面條件不變的情況下悯搔,把 Text 中的文本變成 "Hg\nHg" 這樣的兩行文本骑丸,可以看到換行后的文本重疊在了一起,所以 StrutStylefontSize 也是會(huì)影響行高

image

另外通危,在 StrutStyle 中還有另外一個(gè)參數(shù)也會(huì)影響行高铸豁,那就是 leading

如下圖所示菊碟,加上了 leading 后才是 Flutter 中對(duì)字體行高完全的控制組合节芥,leading 默認(rèn)為 null ,同時(shí)它的效果也是 fontSize 的倍數(shù)逆害,并且分布是上下均分头镊。

image

所以如下代碼所示,當(dāng) StrutStylefontSize100 魄幕,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ù) ascentdescent 的部分放大咙轩,明顯 ascentdescent 大得多,所以前面的 TextStyleheight 為 2 時(shí)机久,充滿后整體往下偏移臭墨。

image

三、backgroundColor

那么到這里應(yīng)該對(duì)于 Flutter 中關(guān)于文本大小膘盖、度量和行高等有了基本的認(rèn)知胧弛,接著再介紹一個(gè)屬性:TextStylebackgroundColor

介紹這個(gè)屬性是為了和前面的內(nèi)容產(chǎn)生一個(gè)對(duì)比侠畔,并且解除一些誤解结缚。

如下代碼所示,可以看到 StrutStylefontSize100 软棺,height1红竭,按照前面的介紹,藍(lán)色的區(qū)域大小應(yīng)該是和紅色小方塊一樣大喘落。

然后我們?cè)O(shè)置了 TextStylebackgroundColor 為具有透明度的綠色茵宪,結(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,
                )
              ],
            ),
          )

        ),
      ),
    );
  }
image

這是不是很有意思,事實(shí)上也可以反應(yīng)出赌朋,字體的度量其實(shí)一直都是默認(rèn)的 ascent + descent = fontSize凰狞,我們可以改變 TextStyleheight 或者 StrutStyle 來改變行高效果篇裁,但是本質(zhì)上的 fontSize 其實(shí)并沒有變。

如果把輸入內(nèi)容換成 "H\ng" 赡若,如下圖所示可以看到更有意思的效果达布。

image

四、TextBaseline

最后再介紹一個(gè)屬性 :TextStyleTextBaseline,因?yàn)檫@個(gè)屬性一直讓人產(chǎn)生“誤解”逾冬。

關(guān)于 TextBaseline 有兩個(gè)屬性黍聂,分別是 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;
}

如下圖所示霸株,藍(lán)色的線就是 baseLine,從效果可以直觀看到不同 baseLine 下對(duì)齊的位置應(yīng)該在哪里集乔。

image

但是事實(shí)上 baseLine 的作用并不會(huì)直接影響 TextStyle 中文本的對(duì)齊方式去件,F(xiàn)lutter 中默認(rèn)顯示的文本只會(huì)通過 TextBaseline.alphabetic 對(duì)齊的,如下圖所示官方人員也對(duì)這個(gè)問題有過描述 #47512扰路。

image

這也是為什么要用 CustomPaint 展示的原因尤溜,因?yàn)橛媚J(rèn) Text 展示不出來。

舉個(gè)典型的例子汗唱,如下代碼所示宫莱,雖然在 RowText 上都是用了 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ì)齊”。

image

自從际插,關(guān)于 Flutter 中的字體相關(guān)的“冷”知識(shí)介紹完了碘耳,不知道你“無用”的知識(shí)有沒有增多呢?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末框弛,一起剝皮案震驚了整個(gè)濱河市辛辨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瑟枫,老刑警劉巖斗搞,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異慷妙,居然都是意外死亡僻焚,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門景殷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溅呢,“玉大人澡屡,你說我怎么就攤上這事「谰桑” “怎么了驶鹉?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長铣墨。 經(jīng)常有香客問我室埋,道長,這世上最難降的妖魔是什么伊约? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任姚淆,我火速辦了婚禮,結(jié)果婚禮上屡律,老公的妹妹穿的比我還像新娘腌逢。我一直安慰自己,他們只是感情好超埋,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布搏讶。 她就那樣靜靜地躺著,像睡著了一般霍殴。 火紅的嫁衣襯著肌膚如雪媒惕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天来庭,我揣著相機(jī)與錄音妒蔚,去河邊找鬼。 笑死月弛,一個(gè)胖子當(dāng)著我的面吹牛肴盏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播尊搬,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼叁鉴,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了佛寿?” 一聲冷哼從身側(cè)響起幌墓,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎冀泻,沒想到半個(gè)月后常侣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡弹渔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年胳施,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肢专。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡舞肆,死狀恐怖焦辅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情椿胯,我是刑警寧澤筷登,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站哩盲,受9級(jí)特大地震影響前方,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜廉油,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一惠险、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧抒线,春花似錦班巩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至旱物,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間卫袒,已是汗流浹背宵呛。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留夕凝,地道東北人宝穗。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像码秉,于是被迫代替她去往敵國和親逮矛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348