React Native 自定义字体实践指南

内容作者为基因宝前端团队【王庆洋】

基因宝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)

image.png

所以设置 lineHeight 可以统一UI,一般来说不同的 fontSize 对应的 lineHeight 都是UI同学定的,大概是 lineheightfontSize 的1.3倍左右,如果 lineheight 过小会导致某些字显示不全。
以上是当前工程中 Text 组件的封装情况

遇到的问题

思源黑体本身的问题

思源黑体本身的问题网上很多讨论,把字体文件放到工程中发现问题比较大,主要有两点:

  1. 首先是Text 未设置 lineHeight 的时候 Text 占空间非常大,
  2. 其次即使设置了lineheight 也不居中显示

这两个问题其实只是一个问题,只要能通过设置 lineHeight 正确的控制 Text 的显示范围,那就可以正常使用。

经发现网上讨论的思源黑体大都是比较旧的 1.0 版本版本,我拿到手上的也是旧版本,然后我在官方 GitHub 上找到了最新的2.002版本,解决了问题,接下来的问题就是正确的设置 lineHeight

image.png
image.png

与现有代码的结合

由于工程中原来就有大量 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 :

  1. Text 嵌套的子组件没有设置,要继承父组件的样式。
  2. Text 嵌套的父组件没有设置,因为每个子组件都有单独设置的 fontSize

对于第一种情况,不需要做任何修改,只要保证父组件有 fontSize 就可以了,对于第二种情况,则需要特殊处理。

在嵌套的时候,如果多个子组件设置了 lineHeight , 会影响整个 Text 组件的行高,导致显示不全,这个问题很严重,所以 Text 嵌套的情况下不需要给子组件传默认 lineHeight,所以约定了一个特殊值 null , 只有当 lineHeightundefined 的时候,才会设置默认值,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 较大的子组件的时候,会裁剪一部分子组件,导致显示不全。


  测试代码
  测试代码

image.png

所幸当前项目这样的代码不多,可以用这正则匹配一下全部代码,找出子组件嵌套

找到所有 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中引入的,这给了我很大的帮助。如果你有更好的办法或者我有什么错误,欢迎指教和指出。

你可能感兴趣的:(React Native 自定义字体实践指南)