Flutter自定义字体你想知道的!

8,222 阅读5分钟

在实际开发中你的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.cn/post/684490…

接下来上干货--------->

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 没有获取流的方法。