Flutter 上預設的文字和字型知識點

戀貓de小郭發表於2020-05-20

我們都知道在 Flutter 中可以通過 fontFamily 來引入第三方字型,例如通常會將 svg 圖示轉換為 iconfont.ttf 來實現向量圖示的入,而一般情況下我們是不會設定 fontFamily 來使用第三方字型, 那預設情況下 Flutter 使用的是什麼字型呢?

會出現這個疑問,是因為有一天設計給我發了下面那張圖,問我 “為什麼應用在蘋果平臺上的英文使用的是 PingFang SC 字型而不是 .SF UI Display ? 正如下圖所示,它們的 G 字母在顯示效果上會有所差異,比如 平方的 G 有明顯的轉折線。

Flutter 上預設的文字和字型知識點

這時候我不禁產生的好奇,在 Flutter 中引擎預設究竟是如何選擇字型?

通過官方解釋,在 typography.dart 原始碼中可以看到,

  • Flutter 預設在 Android 上使用的是 Roboto 字型;
  • 在 iOS 上使用的是 .SF UI Display 或者 .SF UI Text 字型。

The default font on Android is Roboto and on iOS it is .SF UI Display or .SF UI Text (SF meaning San Francisco). If you want to use a different font, then you will need to add it to your app.

Flutter 上預設的文字和字型知識點

那理論上在 iOS 使用的就是 .SF UI Display 字型才對,因為如下原始碼所示,在 Typography 中當 platformiOS 時,使用的就是 Cupertino 相關的 TextTheme,而 Typography 中的 whiteblack 屬性最終會應用到 ThemeDatadefaultTextThemedefaultPrimaryTextThemedefaultAccentTextTheme 中,所以應該是使用 .SF 相關字型才會,為什麼會顯示的是 PingFang SC 的效果?

 factory Typography({
    TargetPlatform platform = TargetPlatform.android,
    TextTheme black,
    TextTheme white,
    TextTheme englishLike,
    TextTheme dense,
    TextTheme tall,
  }) {
    assert(platform != null || (black != null && white != null));
    switch (platform) {
      case TargetPlatform.iOS:
        black ??= blackCupertino;
        white ??= whiteCupertino;
        break;
      case TargetPlatform.android:
      case TargetPlatform.fuchsia:
        black ??= blackMountainView;
        white ??= whiteMountainView;
    }
    englishLike ??= englishLike2014;
    dense ??= dense2014;
    tall ??= tall2014;
    return Typography._(black, white, englishLike, dense, tall);
  }
複製程式碼

為了搞清不同系統上字型的區別,在查閱了資料後可知:

  • 預設在 iOS 上:

    • 中文字型:PingFang SC

    • 英文字型:.SF UI Text.SF UI Display

  • 預設在 Android 上:

    • 中文字型:Source Han Sans / Noto

    • 英文字型:Roboto

也就是就 iOS 上除了 .SF 相關的字型外,還有 PingFang 字型的存在,這時候我突然想起在之前的 《Flutter完整開發實戰詳解(十七、 實用技巧與填坑二)》 中,因為國際化多語言在 .SF 會出現顯示異常,所以使用了 fontFamilyFallback 強行指定了 PingFang SC

  getCopyTextStyle(TextStyle textStyle) {
    return textStyle.copyWith(fontFamilyFallback: ["PingFang SC", "Heiti SC"]);
  }
複製程式碼

Flutter 上預設的文字和字型知識點

終於破案了,因為當 fontFamily 沒有設定時,就會使用 fontFamilyFallback 中的第一個值將作為首選字型,而在 fontFamilyFallback 中是順序匹配的,當fontFamilyfontFamilyFallback 兩者都不提供,則將使用預設平臺字型。

而在 1.12.13 版本下測試發現 .SF 導致的問題已經修復了,所以只需要將 fontFamilyFallback 相關的程式碼去除即可。

那在 iOS 上使用 .SF 字型有什麼好處? 按照網路上的說法是:

SF Text 的字距及字母的半封閉空間,比如 "a"! 上半部分會更大,因其可讀性更好,適用於更小的字型; SF Display 則適用於偏大的字型。具體分水嶺就是 20pt , 即字型小於 20pt 時用 Text ,大於等於 20pt 時用 Display

更棒的是由於 SF 屬於動態字型,TextDisplay 兩種字型族是系統動態匹配的,也就是說你不用費心去自己手動調節,系統自動根據字型的大小匹配這兩種顯示模式。

那能不能在 Android 上也使用.SF 字型呢?按照官方的說法:

  • 在使用 Material package 時,在 Android 上使用的是 ·Roboto font· ,而 iOS 使用的是 San Francisco font(SF)
  • 在使用 Cupertino package 時,預設主題始終使用 San Francisco font(SF)

但是因為 San Francisco font license 限制了該字型只能在 iOS、macOS 或 tvOS 上執行使用,所以如果使用了 Cupertino 主題的話,在 Android 上執行時使用 fallback font。

所以你覺得能不能在 Android 上使用?

最後再補充下,在官方的 architecture 中有提到,在 Flutter 中的文字呈現邏輯是有分層的,其中:

  • 衍生自 Minikin 的 libtxt 庫用於字型選擇,分隔行等;
  • HartBuzz 用於字形選擇和成型;
  • Skia作為 渲染 / GPU後端;
  • 在 Android / Fuchsia 上使用 FreeType 渲染,在 iOS 上使用CoreGraphics 來渲染字型 。

那讀完本篇,你奇奇怪怪的知識點有沒有增加?

Flutter 上預設的文字和字型知識點

相關文章