这次的 Flutter 小技巧是字体渲染,虽然是小技巧但是内容略长,可能大家在日常开发中不会特别关心字体相关的部分,而这将是一篇你平时可能用不到 ,但是遇到问题就会翻出来的文章。
本篇将快速普及一些字体渲染相关的基础,解决一些因为字体而导致的异常问题,并穿插一些实用小技巧,内容篇幅可能略长,建议先 Mark 后看。
一、字体库首先,问一个我经常问的面试题:Flutter 在 Android 和 iOS 上使用了哪些字体?
如果你恰好看过 typography.dart 的源码和解释,你可以会有初步结论:
Android 上使用的是 Roboto 字体;iOS 上使用的是 .SF UI Display 或者 .SF UI Text 字体;但是,如果你再进一步去了解就会发现,在加上中文显示之后,结论应该是:
默认在 iOS 上:中文字体:PingFang SC (繁体还有 PingFang TC 、 PingFang HK)英文字体:.SF UI Text / .SF UI Display默认在 Android 上:中文字体:Source Han Sans / Noto英文字体:Roboto那这时候你可能会问:.SF 没有中文,那可以使用 PingFang 显示英文吗? 答案是可以的,但是字形和字重会有微妙区别, 例如下图里的 G 就有很明显的不同。
那如果加上韩文呢?这时候 iOS 上的 PingFang 和 .SF 就不够用了,需要调用如 Apple SD Gothic Neo 这样的超集字体库,而说到这里就需要介绍一个 Flutter 上你可能会遇到的 Bug。
如下图所示,当在使用 Apple SD Gothic Neo 字体出现中文和韩文同时显示时,你可能会察觉一些字形很奇怪,比如【推广】这两个字,其中【广】这个字符在超集上是不存在的,所以会变成了中文的【广】,但是【推】字用的还是超集里的字形。
这种情况下,最终渲染的结果会如下图所示,解决的思路也很简单,小技巧就是给 TextStyle 或者 Theme 的 fontFamilyFallback 配置上 ["PingFang SC" , "Heiti SC"] 。
另外,如果你还对英文下 .SF UI Display 和 `SF UI Text 之间的关系困惑的话,那其实你不用太过纠结,因为从 SF 设计上大概意思上理解的话:
.SF Text 适用于更小的字体;.SF Display 则适用于偏大的字体,分水岭大概是 20pt 左右,不过 SF(San Francisco) 属于动态字体,系统会动态匹配。
二、Flutter Text虽然上面介绍字体的一些相关内容,但是在 Flutter 上和原生还是有一些差异,在 Flutter 中的文本呈现逻辑是有分层的,其中:
衍生自 Minikin 的 libtxt 库用于字体选择,分隔行等;HartBuzz 用于字形选择和成型;Skia作为 渲染 / GPU后端;在 Android / Fuchsia 上使用 FreeType 渲染,在 iOS 上使用CoreGraphics 来渲染字体 。Text Height那如果这时候我问你一个问题: 一个 fontSize: 100 的 H 字母需要占据多大的高度 ?你会回答多少?
首先,我们用一个 100 的红色 Container 和 fontSize: 100 的 H 文本做个对比,可以看到 H 文本所在的蓝色区域其实是需要大于 100 的红色区域的。
事实上,前面的蓝色区域是字体的行高,也就是 line height,关于这个行高,首先需要解释的就是 TextStyle 中的 height 参数。
默认情况下 height 参数是 null,当我们把它设置为 1 之后,如下图所示,可以看到蓝色区域的高度和红色小方块对齐,变成了 100 的高度,也就是行高变成了 100 ,而 H 字母完整地显示在了蓝色区域内。
那 height 是什么呢?首先 TextStyle 中的 height 参数值在设置后,其效果值是 fontSize 的倍数:
当 height 为空时,行高默认是使用字体的量度(这个量度后面会有解释);当 height 不是空时,行高为 height * fontSize 的大小;如下图所示,蓝色区域和红色区域的对比就是 height 为 null 和 1 的对比高度。
所以,看到这里你又知道了一个小技巧:当文字在 Container “有限高度” 内容内无法居中时,可以考虑调整 TextStyle 中的 height 来实现 。
当然,这时候如果你把 Container 的 height:50 去掉,又会是另外一个效果。
所以 height 参数和文本渲染的高度之间是成倍数关系,具体如下图所示,同时最需要注意的点就是:文本内容在 height 里并不是居中,这里的 height 可以类比于调整行高。
另外,文本中的除了 TextStyle 下的 height 之外,还是有 StrutStyle 参数下的height ,它影响的是字体的整体量度,也就是如下图所示,影响的是 ascent - descent 的高度。
那你说它和 TextStyle 下的 height 有什么区别? 如下图所示例子:
StrutStyle的 froceStrutHeight 开启后,TextStyle 的 height 不会生效;StrutStyle设置 fontSize:50 影响的内容和 TextStyle 的fontSize:100 影响的内容不一样;另外在 StrutStyle里还有一个叫 leading 的 参数,加上了 leading 后才是 Flutter 中对字体行高完全的控制组合,leading 默认为 null ,同时它的效果也是 fontSize 的倍数,并且分布是上下均分。
所以,看到这里你又知道了一个小技巧:设置 leading 可以均分高度,所以如下图所示,也可以用于调整行间距。