在實際開發中你的Boss可能會認為蘋方字型不錯,但是 Android 字型就不是很 Nice,或者一些其他場景,需要讓我們使用某一指定字型。
在這篇文章你可以學到 Flutter 自定義字型基本使用姿勢,混合工程字型檔案共用優化思路。
Flutter 預設字型
FLutter 字型預設不會跟隨系統, Android 端預設使用 Roboto,而在 iOS 端預設使用 San Francisco。
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'),
),
),
],
),
)
],
),
)
複製程式碼
上面我們簡單介紹了 Flutter 中自定義字型的使用,但是對 UI 敏感的朋友應該發現了 Tab 上的字型並沒有適配成全域性的字型。這是因為 Tab 在使用 Style 時並沒有使用 theme 中的 Style 進行 merge,那麼解決方式也很明瞭了
混合工程字型問題
如果我們專案是 Native + Flutter 混合工程,如果原生和 Flutter 都需要使用字型A。如果在 Native 和 Flutter Module中都新增字型A的話,會造成我們應用包增大一個字型A的大小,這是不能忍受的。
由於 UI 或 Boss 一般不會認為 iOS 字型有什麼問題,且本人是 Android 開發,所以這裡就分析下 Android 這邊情況,可能很騷但是有效。接下來我們看下Android Apk包目錄結構:
總所周知 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:
使用靜態載入方式必須得在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 沒有獲取流的方法。