在混编开发中,我们经常遇到要全局替换当前字体的需求,在Native开发中,我们通常会加载Asset或者下载的字体文件,那么在Flutter中,如何直接使用Native的字体文件呢?
毕竟大部分的字体文件都毕竟大,特别是一些字体还有加密策略,如果在Flutter中再创建一份字体文件,既浪费空间,而且也是一种重复代码,所以,我们需要在Flutter端,获取Native的字体文件。
在Flutter中,系统给我们提供了FontLoader,来动态加载字体,与前面的做法一样,我们创建一个Native接口,来获取Native传来的Byte数据流,并借助FontLoader来加载字体。
FontLoader加载字体数据
为了提高传输的效率,我们使用BasicMessageChannel来作为Channel的实现,这些在我们讲解Flutter与Native的通信机制中,都已经演示过了,我们直接拿来Google的Demo代码,修改下需要的内容,将FontLoader引入,代码如下所示。
import 'dart:async';import 'dart:convert';import 'package:flutter/services.dart';class NativeFontApi {NativeFontApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger;final BinaryMessenger? _binaryMessenger;Future<void> loadNativeFont(String fontFamily) async {final BasicMessageChannel<ByteData> channel = BasicMessageChannel<ByteData>('dev.flutter.pigeon.NativeFontApi.loadNativeFont',const BinaryCodec(),binaryMessenger: _binaryMessenger,);try {final uInt8List = utf8.encoder.convert(fontFamily);final Future<ByteData> fontData = _loadFontFileByteData(uInt8List.buffer.asByteData(), channel);if (await fontData != ByteData(0)) {final FontLoader fontLoader = FontLoader(fontFamily);fontLoader.addFont(fontData);fontLoader.load();}} catch (e) {return;}}Future<ByteData> _loadFontFileByteData(ByteData data, BasicMessageChannel channel) async {final ByteData? fontData = await channel.send(data);if (fontData != null) {return fontData;} else {return ByteData(0);}}}
在加载好字体数据之后,我们在代码中就可以直接使用看,这和在配置文件中新增字体后的使用方式一样,直接指定fontFamily即可,代码如下所示。
Text(model[index].bookName ?? "",style: const TextStyle(fontSize: 16,fontFamily: 'xxx_Medium_60',),)
唯一需要注意的是,我们需要在程序启动时,初始化我们的字体文件,代码如下所示,通过loadNativeFont调用Channel来加载字体文件。
NativeFontApi().loadNativeFont('xxx_Medium_60');
Native实现
我们仿照pigeon的实现方式,来创建自己的FontBridgeApi,之所以没通过pigeon直接生成,那是因为pigeon还不支持生成Byte数组的方式,所以我们只能自己来写,代码如下所示。
// Autogenerated from Pigeon (v1.0.15), do not edit directly.// See also: https://pub.dev/packages/pigeonpackage com.qidian.QDReader.flutter;import java.nio.ByteBuffer;import io.flutter.plugin.common.BasicMessageChannel;import io.flutter.plugin.common.BinaryCodec;import io.flutter.plugin.common.BinaryMessenger;@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"})public class FontBridgeApi {public interface NativeFontApi {ByteBuffer loadNativeFont(String familyName);static void setup(BinaryMessenger binaryMessenger, NativeFontApi api) {BasicMessageChannel<ByteBuffer> channel =new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.NativeFontApi.loadNativeFont", BinaryCodec.INSTANCE);if (api != null) {channel.setMessageHandler((message, reply) -> {try {String param = new String(message != null ? message.array() : new byte[0]);ByteBuffer output = api.loadNativeFont(param);reply.reply(output);} catch (Error | RuntimeException ignored) {}});} else {channel.setMessageHandler(null);}}}}
下面就是实现接口,代码如下所示。
private class NativeFontApiImp : FontBridgeApi.NativeFontApi {private val externalAppFontIndexes = intArrayOf(ETConverter.FONT_TYPE_INDEX_XXX_LIGHT,ETConverter.FONT_TYPE_INDEX_XXX_MEDIUM,ETConverter.FONT_TYPE_INDEX_XXXXX)override fun loadNativeFont(familyName: String?): ByteBuffer {val trueTypeFolder = File(ApplicationContext.getInstance().filesDir, ETConverter.FOLDER_TRUE_TYPE_FONTS)if (!trueTypeFolder.exists()) trueTypeFolder.mkdirs()val familyNameIndex = when (familyName) {"XXX_Light" -> 0"XXX_Medium_60" -> 1"XXXXSerif_Bold" -> 2else -> 0}val ttf = File(trueTypeFolder, ETConverter.getFontTypeName(externalAppFontIndexes[familyNameIndex]) + ETConverter.POSTFIX_NEW_TTF)val inputStream: InputStream = FileInputStream(ttf)val output = ByteArrayOutputStream()val buffer = ByteArray(4096)var n = 0while (true) {try {if (-1 == inputStream.read(buffer).also { n = it }) {inputStream.close()output.close()break}} catch (e: IOException) {e.printStackTrace()}output.write(buffer, 0, n)}return ByteBuffer.allocateDirect(output.toByteArray().size).put(output.toByteArray())}}
我们放到前面pigeon统一实现的地方进行初始化,代码如下。
FontBridgeApi.NativeFontApi.setup(flutterEngine.dartExecutor, NativeFontApiImp())
优化
通过上面的方式,我们很轻松就实现了Flutter端加载Native的字体文件,但是在代码实现过程中,实际上有些地方是可以进行优化的,例如在Flutter中加载字体的异步方法中,我们可以构建一个枚举,根据不同的状态值,来修改代码的执行逻辑,例如增加:「加载中」、「加载失败」等状态,这样在程序异常的时候,可以判断是否需要跳过后面的加载流程、或者是重新执行加载流程,可以增加代码的鲁棒性。
// 状态enum NativeFontLoadState { loading, failed, complete, notFound }// 返回枚举状态Future<NativeFontLoadState> loadFontIfNeeded(String fontFamily)
全局字体
在Flutter中,我们通常会根据自己项目的特点,封装一些Text组件,那么在这些组件中,就可以直接指定fontFamily,这样在业务开发时,就不需要重复指定fontFamily了,直接使用XXXText即可。
除了这种方式以外,还可以在APP的themeData中,直接指定fontFamily,代码如下:
theme: ThemeData(fontFamily: xxxx,),
这样可以为子组件提供默认的字体支持。如果在某些场景下需要修改默认字体,那么重新给Text设置不同的fontFamily覆盖即可。
向大家推荐下我的网站 https://xuyisheng.top/ 专注 Android-Kotlin-Flutter 欢迎大家访问

