内容作者为基因宝前端团队【王庆洋】
基因宝APP 目前 Android 和 iOS 都用的是苹方字体,但是表现得不如意,所以要把 Android 端更换为思源黑体。
现有适配规则
首先,现在工程的代码中 Text
组件是基于RN Text组件的封装,封装中主要做了下面的事情:
- 区分 Android 和 iOS,所有
style
都改写传入fontFamily
- Text.android.js 中根据
fontWeight
设置不同的fontFamily
- Text.ios.js 简单了传入了
fontFamily
另外,在工程 Themes 中预制了多个 fontGroup
const Themes = {
fontGroup8: {
fontSize: 8,
lineHeight: 10,
},
// ...
}
在实际使用中都规范的用下面的方法使用 Text
组件
Genebox
const styles = StyleSheet.create({
tilte: {
...Themes.fontGroup8,
// others...
}
})
至于为什么要使用 lineHeight
,是因为不同的字体在默认情况下所占的空间不一样,不设置的话无法做到UI统一,比如下图相同的代码却有不同的显示效果。
我是标题
![i(https://upload-images.jianshu.io/upload_images/25979548-3ded7c070541f15a.png&originHeight=290&originWidth=656&size=24914&status=done&style=none&width=328?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
所以设置
lineHeight
可以统一UI,一般来说不同的 fontSize
对应的 lineHeight
都是UI同学定的,大概是 lineheight
是 fontSize
的1.3倍左右,如果 lineheight
过小会导致某些字显示不全。
以上是当前工程中
Text
组件的封装情况
遇到的问题
思源黑体本身的问题
思源黑体本身的问题网上很多讨论,把字体文件放到工程中发现问题比较大,主要有两点:
- 首先是
Text
未设置lineHeight
的时候Text
占空间非常大, - 其次即使设置了lineheight 也不居中显示
这两个问题其实只是一个问题,只要能通过设置 lineHeight
正确的控制 Text
的显示范围,那就可以正常使用。
经发现网上讨论的思源黑体大都是比较旧的 1.0 版本版本,我拿到手上的也是旧版本,然后我在官方 GitHub 上找到了最新的2.002版本,解决了问题,接下来的问题就是正确的设置 lineHeight
与现有代码的结合
由于工程中原来就有大量 lineHeight
的设置,所以只要顺势将没有设置 lineHeight
的组件全部设置对应的行高就可以了,所以再重写 style
的代码中加入了添加默认 lineHeight
的样式代码:
const { fontSize } = style;
let { lineHeight } = style;
if (fontSize && !lineHeight) {
lineHeight = Themes.getLineHeight(fontSize);
}
const newStyle = [style, { lineHeight }];
这样只能解决一般使用中没有设置 lineHeight
的问题,但是没有处理没有设置 fontSize
的情况,正常情况下除了测试代码是不会不设置 fontSize
的,除非是 Text
嵌套的时候。
现有工程中有两类代码没有设置 fontSize
:
-
Text
嵌套的子组件没有设置,要继承父组件的样式。 -
Text
嵌套的父组件没有设置,因为每个子组件都有单独设置的fontSize
。
对于第一种情况,不需要做任何修改,只要保证父组件有 fontSize
就可以了,对于第二种情况,则需要特殊处理。
在嵌套的时候,如果多个子组件设置了 lineHeight
, 会影响整个 Text
组件的行高,导致显示不全,这个问题很严重,所以 Text
嵌套的情况下不需要给子组件传默认 lineHeight
,所以约定了一个特殊值 null
, 只有当 lineHeight
为 undefined
的时候,才会设置默认值,null 的时候不设置默认值。
为了统一设置子组件的 lineHeight
,我重写了 render
中的 children
,所以最后的代码是这样的
render() {
const { style, children, ...restProps } = this.props;
const newStyle = StyleSheet.flatten(style);
const { fontSize, lineHeight } = newStyle;
let newLineHeight = lineHeight;
if (fontSize) {
// 这里只处理没有传 lineHeight 的情况,传空值认为是特意不设置 lineHeight
newLineHeight = lineHeight ?? Themes.getLineHeight(fontSize);
}
return (
{
React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
const { style: childStyle } = child.props;
return React.cloneElement(child, {
style: [StyleSheet.flatten(childStyle), { lineHeight: null }]
});
}
return child;
})
}
);
}
这样有一个弊端,在 Text
嵌套的时候,父组件的 fontSize
不能比子组件小,因为小的 fontSize
对应的较小的 lineHeight
应用到 fontSize 较大的子组件的时候,会裁剪一部分子组件,导致显示不全。
测试代码
测试代码
所幸当前项目这样的代码不多,可以用这正则匹配一下全部代码,找出子组件嵌套
找到所有 Text
嵌套的所有代码后,逐一检查两点:
- 父组件全部设置
fontSize
- 父组件的
fontSize
不能比任何子组件的fontSize
小
到这里,基本上就没有问题了。
其他问题
- 空格占位
当前项目中有几处 Text
中用空格占位,更换字体后,空格的占位的宽度变化很明显,这种方式不再适用,推荐使用
内嵌
- Animated.Text
由于 Animated.Text
是由 createAnimatedComponent()
封装的所以再次封装即可
import { Animated } from 'react-native';
import Text from './Text';
const AnimatedText = Animated.createAnimatedComponent(Text);
Animated.Text = AnimatedText;
总结
得益于现有工程有比较的好的规范和架构,更换字体没有碰到很大的问题,最重要的是整个App的Text 组件都有一个统一的引入入口,而不是直接从RN中引入的,这给了我很大的帮助。如果你有更好的办法或者我有什么错误,欢迎指教和指出。