Flutter自定義字型你想知道的!

Joker_Fu發表於2020-04-02

在實際開發中你的Boss可能會認為蘋方字型不錯,但是 Android 字型就不是很 Nice,或者一些其他場景,需要讓我們使用某一指定字型。

在這篇文章你可以學到 Flutter 自定義字型基本使用姿勢,混合工程字型檔案共用優化思路。

Flutter 預設字型

FLutter 字型預設不會跟隨系統, Android 端預設使用 Roboto,而在 iOS 端預設使用 San Francisco。

Flutter 預設字型

Flutter 自定義字型載入

Flutter 使用自定義字型可以分為靜態和動態 2 種方式:

- 靜態載入

首先新增字檔案,接著在 .ymal 中宣告,最後 get 就好了。配置如下:

新增字型步驟

- 動態載入

使用靜態靜態載入,字型檔案得在應用包內,Flutter 為我們提供了 FontLoader 便於我們實現動態載入,此時字型可以在網路、本地或者應用內。不過不過又注意事項。

核心程式碼:

    // 構建 loader
    var fontLoader = FontLoader('FenPinYinTi2');
    // 獲取字型 (Future<ByteData>) 裝載到 loader
    fontLoader.addFont(fetchFontByteData());
    // 載入字型
    await fontLoader.load();
複製程式碼

敲黑板:唯一遺憾的是addFont註釋裡說的目前僅支援ttf字型*

/// Registers a font asset to be loaded by this font loader.

/// The [bytes] argument specifies the actual font asset bytes. Currently,

/// only TrueType (TTF) fonts are supported.

獲取應用Assets中字型

    // 這裡 DefaultAssetBundle.of(context) 也可以替換成 rootBundle
    // 根據自身情況決定
    Future<ByteData> fetchFontByteData() => DefaultAssetBundle.of(context).load('fonts/FenPinYinTi2.ttf');
複製程式碼

完整程式碼:

    // load font file
    Future loadFontFile() async {
        var fontLoader = FontLoader('FenPinYinTi2');
        fontLoader.addFont(fetchFontByteData());
        await fontLoader.load().catchError((e) {
          loge("loadFontFile erro: $e");
        });
        setState(() {});
    }
    
    Future<ByteData> fetchFontByteData() => DefaultAssetBundle.of(context).load('fonts/FenPinYinTi2.ttf');
複製程式碼

同理你可以改造 fetchFontByteData,通過 NetworkAssetBundle 或者 Dio 獲取網路上字型,當然你也可以獲取 SD 卡中字型,這裡就不在贅述。

Flutter 字型使用

1. 全域性配置 fontFamily

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          navigatorKey: navigatorKey,
          theme: ThemeData(
            fontFamily: Platform.isAndroid ? 'FenPinYinTi2' : null,
          ),
          home: HomePage(),
        );
      }
    }
複製程式碼

2. 區域性配置 fontFamily

    Container(
        color: Colors.white,
        child: Column(
          children: <Widget>[
            TabBar(
                controller: TabController(length: 2, vsync: this),
                labelStyle: TextStyle(
                  fontSize: 24,
                ),
                unselectedLabelStyle: TextStyle(
                  fontSize: 24,
                ),
                labelColor: AppColors.textBlack,
                unselectedLabelColor: AppColors.textGrey,
                tabs: [
                  Tab(child: Text('標籤1')),
                  Tab(child: Text('標籤2')),
                ]),
            Align(
              child: Column(
                children: <Widget>[
                  Text(
                    '自定義字型1',
                    style: TextStyle(
                      fontSize: 24,
                    ),
                  ),
                  SizedBox(
                    height: 40,
                  ),
                  Container(
                    color: Colors.red,
                    child: Text(
                      '自定義字型2',
                      style:
                          TextStyle(fontSize: 24, fontFamily: 'SourceHanSansCN'),
                    ),
                  ),
                ],
              ),
            )
          ],
        ),
      )
複製程式碼

效果圖1

上面我們簡單介紹了 Flutter 中自定義字型的使用,但是對 UI 敏感的朋友應該發現了 Tab 上的字型並沒有適配成全域性的字型。這是因為 Tab 在使用 Style 時並沒有使用 theme 中的 Style 進行 merge,那麼解決方式也很明瞭了

混合工程字型問題

如果我們專案是 Native + Flutter 混合工程,如果原生和 Flutter 都需要使用字型A。如果在 Native 和 Flutter Module中都新增字型A的話,會造成我們應用包增大一個字型A的大小,這是不能忍受的。

由於 UI 或 Boss 一般不會認為 iOS 字型有什麼問題,且本人是 Android 開發,所以這裡就分析下 Android 這邊情況,可能很騷但是有效。接下來我們看下Android Apk包目錄結構:

Assets 目錄結構

總所周知 Android O開始,Android 支援了xml中配置字型,且字型存放在 res/font 中,還有另一種方式 就是放在 Assets/某一資料夾, 然後通過程式碼的方式進行設定,這裡也不贅述,我這裡使用的就是另一種方式。

不清楚的朋友推薦檢視:juejin.im/entry/59e84…

接下來上乾貨--------->

Native + Flutter 字型共用思路有如下幾種方式:

1. 字型存放在網路(理論最佳)

Flutter 通過 FontLoader 比較容易實現,但是 Native 需要證書和簽名什麼的,就算這2個都搞到了,還需要和 Google Play 服務,明白的人遂放棄...

2. 字型存放在 Flutter fonts 中

字型放在 Flutter fonts 中,參照上文在 Flutter 中使用是沒有任何問題的。Native 中 通過上圖 知道 Android 打包後,在 Flutter fonts 存放字型會放在 assets/flutter_assets/fonts 中,這樣我們只需要通過 Typeface.createFromAsset 拿到 Typeface 進行使用。

這一思路使用上完全沒毛病,但是 iOS 不需要修改字型,這樣的話字型也會被打包到 iOS 的 ipa 中,當然可以讓 iOS 同學進行刪除後打包(手動滑稽)。

3. 字型存放在 Native Assest 某一目錄

通過上圖 知道 Android 打包後,在 Flutter fonts 存放字型會放在 assets/flutter_assets/fonts 中,所以在 Native 中字型自然按這個目錄結構存放,Native 使用可以參照第二點。而在 Flutter 就可以通過 FontLoader 來載入使用。

這一思路使用上完全沒毛病, iOS 也不會打包多餘的字型檔案。問題是 FontLoader 目前只能載入ttf,而且你可能想問如果想使用靜態載入載入方式怎麼辦?使用靜態載入方式也很簡單,只要要將靜態載入方式中的ttf/otf,替換成一個0kb的就可以了(直接弄個0kb的txt改成對應字型格式字尾)。

我們專案中使用得就是方式3,這裡給出結構,使用和文章開頭並無區別。

Native:

真實字型檔案

Flutter:

0KB空檔案

使用靜態載入方式必須得在font下面放入.ymal中的字型,或者同名空檔案, 否則會報錯:

Error: unable to locate asset entry in pubspec.yaml: "fonts/notosanshans-demilight.otf".

導致字型(包括 Flutter 自帶得字型圖示)不能打包到 Assets 中,說到底就是利用了打包合併資源。

最後說下為什麼 Native 不放在 res/font ,因為本人嘗試過通過 channel 傳流給 Flutter,但是放在 res/font 中 Native 沒有獲取流的方法。

相關文章