Loading... ## 什么是可变字体(Variable Font) 普通的字体可能有多个字重(比如 light、regular、bold)等,他们分别是一个个独立的静态的文件,在某些情况下,我们可能需要在应用中使用自定义的字体。可是,相对于西文字体来说,中文字体的体积相对偏高,单个字体文件的大小可能就有 15 MB 以上,并且在某些情况下,我们可能需要引入多个字重的字体,比如粗体和细体,无形中又增大了静态文件的大小。而可变字体,简单一点理解就是将多个字重的字体合并为一个字体。 ## 可变字体的选择 虽然可变字体的好处众多,但可惜的是,对于中文字体来说,可选择的字体太少,到目前为止,较为优秀的中文可变字体只有 [MiSans](https://hyperos.mi.com/font/rare-word) 的 VF 版本,以及 [思源黑体](https://github.com/adobe-fonts/source-han-sans/tree/release/Variable) 的 VF 版本,值得高兴的是,这两个字体均提供了免费的商用。接下来我以 MiSans 为例。 ## 引入可变字体 首先在 [MiSans](https://hyperos.mi.com/font/rare-word) 官网下载字体,下载出来后解压,其中有一个名为 `MiSans VF.ttf` 的文件,就是我们需要的可变字体。可以看到体积仅有 19 MB,如果还是觉得打包体积过大,可以采取应用下载后分发的方式加载。这样也可以减少打包的大小。 ### 创建文件夹 在 Flutter 项目的根目录下创建一个 `font` 文件夹,注意不是 lib 目录下,而是和 lib 同级,之后将文件拷贝到目录下,完成后的项目结构看起来像这样。  ### 引入字体文件 打开 `pubspec.yaml` 在 `flutter` 块中加入如下代码。 ```yaml fonts: - family: MiSans VF fonts: - asset: fonts/MiSans VF.ttf ``` 修改后的文件看起来像这样。  这里通过静态方法进行引入,会将字体作为静态文件打包到安装包里,会带来一定的体积增加,如果想从网络分发字体,可以使用 `FontLoader` 加载,这里不展开讲。 引入后不要忘记运行一次 `pub get` 。 ### 全局使用字体 找到应用入口的 `MaterialApp()` 块,通常在 `main.dart` 中,分别有两个属性 `theme` 和 `darkTheme` ,用于控制浅色和深色模式的样式,需要对其修改,重新运行应用后,字体就被全局修改为了 MiSans。 ```dart theme: ThemeData( fontFamily: 'MiSans VF', fontFamilyFallback: const ['MiSans VF'], ), darkTheme: ThemeData( fontFamily: 'MiSans VF', fontFamilyFallback: const ['MiSans VF'], ), ``` ## 使用可变字体 可变字体最大的特征就是可变,在 Flutter 中,文字样式属性 `TextStyle` 的定义如下。 ```dart const TextStyle({ this.inherit = true, this.color, this.backgroundColor, this.fontSize, this.fontWeight, this.fontStyle, this.letterSpacing, this.wordSpacing, this.textBaseline, this.height, this.leadingDistribution, this.locale, this.foreground, this.background, this.shadows, this.fontFeatures, this.fontVariations, this.decoration, this.decorationColor, this.decorationStyle, this.decorationThickness, this.debugLabel, String? fontFamily, List<String>? fontFamilyFallback, String? package, this.overflow, }) : fontFamily = package == null ? fontFamily : 'packages/$package/$fontFamily', _fontFamilyFallback = fontFamilyFallback, _package = package, assert(color == null || foreground == null, _kColorForegroundWarning), assert(backgroundColor == null || background == null, _kColorBackgroundWarning); ``` 可以看到其中有几个关键的属性,就拿一般定义字重用的 `fontWeight` 属性来说,查看源码会发现,Flutter 对于 FontWeight 的定义是 100-900 之间的整数值,就像下面这样。 ```dart /// Thin, the least thick. static const FontWeight w100 = FontWeight._(0, 100); /// Extra-light. static const FontWeight w200 = FontWeight._(1, 200); /// Light. static const FontWeight w300 = FontWeight._(2, 300); /// Normal / regular / plain. static const FontWeight w400 = FontWeight._(3, 400); /// Medium. static const FontWeight w500 = FontWeight._(4, 500); /// Semi-bold. static const FontWeight w600 = FontWeight._(5, 600); /// Bold. static const FontWeight w700 = FontWeight._(6, 700); /// Extra-bold. static const FontWeight w800 = FontWeight._(7, 800); /// Black, the most thick. static const FontWeight w900 = FontWeight._(8, 900); ``` 也就是说,你不能通过 FontWeight 取一个非整数的值,比如 325。对于普通的字体,基本都遵循这样的规范,比如 300 表示 Light 字重,而 700 表示 Bold 字重,但是对于可变字体来说,不同字重的映射关系可能并不是这样的整数,就拿 MiSans VF 来说,不同字重对应的 FontWeight 是这样的。 ```dart thin = FontVariation('wght', 150.0); extraLight = FontVariation('wght', 200.0); light = FontVariation('wght', 250.0); normal = FontVariation('wght', 305.0); regular = FontVariation('wght', 330.0); medium = FontVariation('wght', 380.0); demiBold = FontVariation('wght', 450.0); semiBold = FontVariation('wght', 520.0); bold = FontVariation('wght', 630.0); heavy = FontVariation('wght', 700.0); ``` 你可能注意到了这里的 FontVariation ,没错,这正是为可变字体准备的特征。 ### FontVariation <div class="tip inlineBlock warning"> 如果想了解更多可变字体的特征,移步 [Variable fonts guide](https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide) </div> 简单的说,可变字体引入了可变轴的概念,这也是为什么仅需要一个字体文件就可以代替多个字体的原因,目前常用的可变轴有五个,分别是字重,宽度,倾斜度,斜体和光学尺寸(MiSans 只支持字重一个可变轴)。 对于字重来说,对应的可变轴标签为 `wght` ,需要注意的是,`wght` 的值是可以自定义的,不同于传统 FontWeight 100-900 之间整数的表示形式,只要在有效范围内的 `wght` 值,都能直接反应到字体上,对于 MiSans ,`wght` 的有效范围是 150-700。 在定义好上面的 FontVariation 后,就可以在 `TextStyle` 中使用了,`TextStyle` 中的 `fontVariation` 是一个 `List<FontVariation?>` ,这样做的原因是,你可以同时定义多个可变轴。 ```dart Text( '可变字体', style: TextStyle(fontVariations: [VFFontWeight.thin], fontSize: 52), ), Text( '可变字体', style: TextStyle(fontVariations: [VFFontWeight.extraLight], fontSize: 52), ), Text( '可变字体', style: TextStyle(fontVariations: [VFFontWeight.light], fontSize: 52), ), Text( '可变字体', style: TextStyle(fontVariations: [VFFontWeight.normal], fontSize: 52), ), Text( '可变字体', style: TextStyle(fontVariations: [VFFontWeight.regular], fontSize: 52), ), Text( '可变字体', style: TextStyle(fontVariations: [VFFontWeight.medium], fontSize: 52), ), Text( '可变字体', style: TextStyle(fontVariations: [VFFontWeight.demiBold], fontSize: 52), ), Text( '可变字体', style: TextStyle(fontVariations: [VFFontWeight.semiBold], fontSize: 52), ), Text( '可变字体', style: TextStyle(fontVariations: [VFFontWeight.bold], fontSize: 52), ), Text( '可变字体', style: TextStyle(fontVariations: [VFFontWeight.heavy], fontSize: 52), ), ``` 上面的代码定义了 10 个标准字重的字体,如果成功运行,在设备上是这样的。  ### FontFeature `font-feature` 并不是可变字体的专利,其他的字体如果遵循 Opentype 规范也可以使用。MiSans 包含了多种特征,具体的特征可以在官网查看,FontFeature 的使用和上面的 FontVariation 类似,参数类型为 `List<FontFeature?>` ,假如想启用等宽数字,可以使用 `FontFeature.enable('thum')`。 ## 有什么用 前面所提到的一些特征,好像传统字体也能做到,代价只是更大的体积而已,但其实可变字体有更多玩法可以使用。就拿字重来说,前面提到,可变字体的 `wght` 在有效范围内都是可以体现在字体上的,也就是说,你甚至可以将 `wght` 的值定义在一个动画曲线上,使得字体的字重可以动态改变,就想一个动画一样。这里我使用 Flutter 提供的 `IntTween` 将字重的范围 150-700 进行插值,同时使用了一个非线性的动画函数。 ```dart animationController = AnimationController(vsync: this, duration: const Duration(seconds: 5), lowerBound: 0, upperBound: 1.0); var value = IntTween(begin: 150, end: 700).animate(CurvedAnimation(parent: animationController, curve: Curves.bounceInOut)); ``` 调用动画之后,会得到一个在 5 秒钟之内,从 150-700 变化的一个非线性的值,`Curves.bounceInOut` 曲线的变化看起来像这样。  将他应用到字体上会发生什么呢?  有趣。 © 允许规范转载 打赏 赞赏作者 微信 赞