满堂花醉三千客,一剑霜寒十四州。这篇文章主要讲述Flutter 小技巧之玩转字体渲染和问题修复相关的知识,希望能为你提供帮助。
这次的 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?
?
?PingFang?
? 显示英文吗? 答案是可以的,但是字形和字重会有微妙区别, 例如下图里的 G 就有很明显的不同。【Flutter 小技巧之玩转字体渲染和问题修复】那如果加上韩文呢?这时候 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 来渲染字体。
那如果这时候我问你一个问题: 一个 ?
? 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?
? 来实现 。当然,这时候如果你把 ?所以 height 参数和文本渲染的高度之间是成倍数关系,具体如下图所示,同时最需要注意的点就是:文本内容在 height 里并不是居中,这里的 height 可以类比于调整行高。?Container?
?? 的 ??height:50?
? 去掉,又会是另外一个效果。
另外,文本中的除了 ?
?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?
? 可以均分高度,所以如下图所示,也可以用于调整行间距。更多行高相关可见 :??《深入理解 Flutter 中的字体“冷”知识》??FontWeight
另外一个关于字体的知识点就是 ?
?FontWeight?
?? ,相信大家对 ??FontWeight?
??不会陌生,比如我们默认的 normal 是 w400,而常用的 bold 是 w700 ,整个??FontWeight?
?列表覆盖 100-900 的数值。那么这里又有个问题:这些Weight 在字体里都能找到对应的粗细吗?
答案是不行的,因为正常情况下如下图所示 ,有些字体库在某些Weight下是没有对应支持,例如
- Roboto 没有 w600
- PingFang 没有高于 w600
那你可能好奇,为什么这里要特意介绍 FontWeight ?因为在 Flutter 3.0 目前它对中文有 Bug!
从下面这张图你可以看到,在 Flutter 3.0 上中文从 100-500 的字重显示是不正常的,肉眼可以看出在 100 - 500 都显示同一个字重。
这个 Bug 来自于当 ?临时解决小技巧也很简单:全局设置 ??SkParagraph?
?? 调用 ??onMatchFamilyStyleCharacter?
??时,??onMatchFamilyStyleCharacter?
??的实现没有选择最接近??TextStyle?
?? 的字体,所以在??CTFontCreateWithFontDescriptor?
??时会带上weight参数但是却没有??familyName?
? ,所以 CTFontCreateWithFontDescriptor`函数就会返回 Helvetica 字体的默认 weight。
?fontFamilyFallback: ["PingFang SC"]?
? 或者 ??fontFamily: PingFang SC?
? 就可以解决,又是 Fallback , 这时候你就会发现,前面介绍的字体常识,可以在这里快速被利用起来。因为 iOS 上中文就是 ?添加的 Fallback之后效果如上图左侧所示, 那 Fallback 的作用是什么??PingFang SC?
?? ,只要 Fallback 回PingFang就可以正常渲染,而这个问题在 Android 模拟器、iOS 真机、Mac 上等会出现,但是 Android 真机上却不会,该问题我也提交在 ??#105014?? 下开始跟进。
前面我们介绍过,系统在多语言中渲染是需要多种字体库来支持,而当找不到字形时,就要依赖提供的 Fallback里的有序列表,例如:
如果在??fontFamily???中找不到字形,则在 ??fontFamilyFallback?? 中搜索,如果没有找到,则会在返回默认字体。另外关于?
?FontWeight?
?? 还有一个“小彩蛋”,在 iOS 上,当用户在辅助设置里开启 Bold Text 之后,如果你使用的是 ??Text?
? 控件,那么默认情况下所有的字体都会变成 w700 的粗体。因为在 ?
?Text?
??内使用了 ??MediaQuery.boldTextOverride?
??判断,Flutter 会接收到 iOS 上用户开启了 Bold Text ,从而强行将 ??fontWeight?
?? 设置为 ??FontWeight.bold ?
?? ,当然如果你直接使用 ??RichText?
? 就 没有这一行为。这时候小技巧就又来了:如果你不希望这些系统行为干扰到你,那么你可以通过嵌套 ?
?MediaQuery?
? 来全局关闭,而类似的行为还有 ??textScaleFactor?
? 和 ??platformBrightness?
?等 。return MediaQuery(
data: MediaQueryData.fromWindow(WidgetsBinding.instance!.window).copyWith(boldText: false),
child: MaterialApp(
useInheritedMediaQuery: true,
),
);
FontFeature
最后再介绍一个冷门参数 FontFeature 。
什么是 ?
?FontFeature?
?? 简单来说就是影响字体形状的一个属性 ,在前端的对应领域里应该是 ??font-feature-settings?
??,它有别于 ??FontFamily?
? ,是用于指定字体内字的形状参数。如下图所示是 ??frac?
?? 分数和 ??tnum?
?? 表格数字的对比渲染效果,这种效果可以在不增加字体库时实现特殊的渲染,另外 ??Feature?
? 也有特征的意思,所以也可以理解为字体特征。
那FontFeature 有什么用呢? 这里又有一个使用小技巧了:当出现数字和文本同时出现,导致排列不对齐时,可以通过给 ?
?Text?
? 设置 ??fontFeatures: [FontFeature("tnum")]?
? 来对齐。例如下图左边是没有设置 fontFeatures 的情况,右边是设置了 ?
?FontFeature("tnum")?
? 的情况,对比之下还是很明显的。更多关于 FontFeature的内容可见 ??《Flutter 上字体的另类玩法:FontFeature 》??三、最后总结一下,本篇内容信息量相对比较密集,主要涉及:
- 字体基础
- Text Height
- FontWeight
- FontFeature
如果你还有什么关于字体的疑问,欢迎留言讨论~
推荐阅读
- 压缩20M文件从30秒到1秒的优化过程
- Spring Boot 微信点餐开源系统
- 项目中常用的19条MySQL优化
- git pull的时候发生冲突的解决方法之“error Your local changes to the following files would be overwritten by merge”
- Java反射注解妙用(获取所有接口说明)
- Spring 自定义注解玩法大全,从入门到…
- 工作5年了,斗胆谈谈我认为技术人做到哪些才能进阿里!
- 那些只有几行,但是却非常牛逼的代码!
- 升级grub 第一分区遇到的问题