flutter web异步加载字体

“早用早踩坑,晚用晚舒服”,欢迎来到今天的踩坑时间。对于ios和安卓应用来说,字体文件往往会被一起打包在应用文件里,即使从网络下载负担也不大。而对于web来说,同步加载的一个几百kb甚至几兆的字体文件对于页面来说简直就是灭顶之灾,现象就是整个卡住、白屏等等,flutter官方文档中也只写了如何从本地引用字体文件,那么就让我们来把web端异步加载字体文件的坑给填上吧!

请注意:flutter仍处于快速发展阶段,以下内容可能已经过时,请根据下方列出的版本信息结合实际情况参考使用。

概述

本文目标是在flutter web环境下使用网络字体资源并实现字体文件的异步加载。
如果您仅需加载本地字体文件可以参考官方文档:使用自定义字体
撰文时使用的主要环境为:

1
2
3
4
5
$ flutter --version
Flutter 1.18.0-11.1.pre • channel beta • https://github.com/flutter/flutter.git
Framework • revision 2738a1148b • 2020-05-13 15:24:36 -0700
Engine • revision ef9215ceb2
Tools • Dart 2.9.0 (build 2.9.0-8.2.beta)

详细记录

加载网络资源

使用http get

首先需要引入官方提供的http包:

1
import 'package:http/http.dart' as http;

为什么说要先引入呢,因为反正我用idea的时候没能给我自动提示出来,所以就手动引入一下。
接下来就是下载字体资源了,感觉和大多数其他语法都是相似的:

1
2
3
4
5
6
7
8
Future<ByteData> loadNetworkByteData(String uri) async {
final response = await http.get(uri);
if (response.statusCode == 200) {
return ByteData.view(response.bodyBytes.buffer);
} else {
throw Exception('Failed to byte data $uri');
}
}

简单到直接使用http.get(url)就可以啦,通过上方的代码我们就获得了字体文件的二进制数据。

使用NetworkAssetBundle

请注意NetworkAssetBundle暂时无法在web上使用,如果不想了解可以直接跳过。

其实在一开始研究如何加载网络资源的时候,我搜索到可以使用 NetworkAssetBundle,使用方法虽然很简单:

1
2
3
Future<ByteData> loadNetworkByteData(String uri) async {
return await NetworkAssetBundle(Uri.base.resolve(uri)).load('');
}

但是报错也来的很迅速:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Launching lib\main.dart on Chrome in debug mode...
Syncing files to device Chrome...
Debug service listening on ws://127.0.0.1:1690/NK_lamQBXUE=
Debug service listening on ws://127.0.0.1:1690/NK_lamQBXUE=
Error: Unsupported operation: Platform._version
at Object.throw_ [as throw] (http://localhost:1624/dart_sdk.js:4773:11)
at Function._version (http://localhost:1624/dart_sdk.js:51829:17)
at Function.get version [as version] (http://localhost:1624/dart_sdk.js:51903:27)
at get _version (http://localhost:1624/dart_sdk.js:51773:27)
at Function.desc.get [as _version] (http://localhost:1624/dart_sdk.js:5267:17)
at Function.get version [as version] (http://localhost:1624/dart_sdk.js:51747:26)
at Object._getHttpVersion (http://localhost:1624/dart_sdk.js:170490:31)
at new _http._HttpClient.new (http://localhost:1624/dart_sdk.js:165842:28)
at Function.new (http://localhost:1624/dart_sdk.js:160942:16)
at new asset_bundle.NetworkAssetBundle.new (http://localhost:1624/packages/flutter/src/services/platform_channel.dart.lib.js:1536:45)
...

我们可以看到日志中摆明了告诉你错误原因是Error: Unsupported operation: Platform._version,并且上方最后一行显示错误出现在NetworkAssetBundle,TAT好吧这个不行,既然现在还不行,那么我就不详细介绍NetworkAssetBundle的用法了,如果你需要具体了解NetworkAssetBundle,请参考官方文档:NetworkAssetBundle class

异步加载字体文件

在这里,我们需要用到一个名为FontLoader的东西,要知道,这玩意儿刚刚出来的时候也不支持web(详细可以看一下flutter的issue#45727),后来看到支持了让我松了一口气,至少可以正常使用。
用法同样是非常简单的:

1
2
3
4
5
Future<void> loadFont() {
var fontLoader = FontLoader('example');
fontLoader.addFont(loadNetworkByteData(fontURI));
return fontLoader.load();
}

在这方法中,我们返回了一个空的Future,用这种方法达到异步加载的问题,在完成字体资源的下载后,页面上的字体会被自动更新。
当然了,如果你需要同步加载,使用async+await即可。

完整样例

请注意:字体当前只支持.ttf格式的字体文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'pages/home.dart';

Future<ByteData> loadNetworkByteData(String uri) async {
final response = await http.get(uri);
if (response.statusCode == 200) {
return ByteData.view(response.bodyBytes.buffer);
} else {
throw Exception('Failed to load font $uri');
}
}

Future<void> loadFont() {
var fontLoader = FontLoader('example');
fontLoader.addFont(loadNetworkByteData('https://font/example.ttf'));
return fontLoader.load();
}

void main() {
loadFont();

runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MyApp',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: HomePage(title: 'MyApp'),
);
}
}

参考文章

坚持原创!欢迎各位客官给我打赏买🍪小饼干吖!✿✿ヽ(°▽°)ノ✿