React Native字体问题解决方案指北

源码已上传 Github: react-native-app-font “怎么又是字体,老常的话题如今还拿出来讲。关于字体适配的解决方式网上一搜几十篇!”。看到标题的烙铁内心一万个xxx疾驰飞腾。But! 我老是会给你们带点什么惊喜。关于 pxToDp、启动缩放 咱们一点不说。本篇博客的主题很简单:如何控制App字体不随系统字体改变? 系统字体改变 通常有两种状况: 

(1)调整系统字体缩放 javascript

(2)修改系统字体样式(方正体、彩云等等)java

Text 组件字体缩放

手机设置中咱们能够调整字体的大小来控制手机字体的显示。通常状况下不少App并无考虑系统字体大小改变所带来的UI影响。因此,当用户调整系统字体大小后,App当前的UI布局由于尺寸未适配的缘由致使布局错乱。如何快速解决这个问题呢?其实官方在字体缩放为咱们提供了解决办法:allowFontScaling。只须要将Text组件的该属性设置为false。当修改系统字体大小时,App中的Text大小就不会随之改变。此时,咱们能够在index入口文件,为Text组件添加全局属性配置,统一将Text组件allowFontScaling设置为false。
react

import { Text } from 'react-native';
const TextRender = Text.render;
Text.render = function (...args) {
    const originText = TextRender.apply(this, args);
    const { style } = originText.props;
    return React.cloneElement(originText, {
        allowFontScaling: false,
    });
};复制代码

TextInput 组件字体缩放

OK,咱们兴奋的打开手机测试一番。忽然发如今登陆输入时,输入框中的字体仍然仍是随着系统字体发生了改变。TextInput中咱们能够经过style设置字体大小,那么是否也可使用allowFontScaling控制呢?继续修改,为TextInput组件全局添加allowFontScaling为false的设置。运行发现 iOS设备一切正常,Android设备没有起到任何效果。因此,为TextInput设置allowFontScaling不能解决Android终端问题。如何解决Android端的问题呢?其实咱们能够利用 PixelRatio 。 官方对 PixelRatio 的解释以下: 
android

PixelRatio class gives access to the device pixel density.ios

翻译:react-native

PixelRatio 类提供了访问设备的像素密度的方法。bash

在 PixelRatio 中提供了四种Api:app

其中 getFontScale 能够获取当前字体大小的缩放比例。官方的解释以下:函数

Returns the scaling factor for font sizes. This is the ratio that is used to calculate the absolute font size, so any elements that heavily depend on that should use this to do calculations. If a font scale is not set, this returns the device pixel ratio. Currently this is only implemented on Android and reflects the user preference set in Settings > Display > Font size, on iOS it will always return the default pixel ratio. @platform android 
 返回字体大小缩放比例。这个比例能够用于计算绝对的字体大小,因此不少深度依赖字体大小的组件须要用此函数的结果进行计算。 若是没有设置字体缩放大小,它会直接返回设备的像素密度。 目前这个函数仅仅在 Android 设备上实现了,它会体现用户选项里的“设置 > 显示 > 字体大小”。在 iOS 设备上它会直接返回默认的像素密度。

能够看到,该Api目前只支持Android平台的使用。在iOS端会出现一些问题。 因此咱们可使用getFontScale来解决Android端TextInput字体缩放问题:
工具

字体大小 = 当前字体大小值 / PixelRatio.getFontScale()

⚠️注意:此解决方法,Android平台请勿在TextInput中将allowTextScaling设置为false。

因此总体的解决方案以下:

封装一个工具function:

/** * TextInput 组件字体适配 */
import { PixelRatio, Platform } from 'react-native';
 
export default function (fontSize) {
    return Platform.OS === 'android' ? fontSize / PixelRatio.getFontScale() : fontSize;
}复制代码

使用方式以下:

textInputFont: {     fontSize: TextInputFontResponsive(18),},复制代码

入口文件中配置TextInput全局属性allowTextScaling设置为false。

import { TextInput, Platform } from 'react-native';
const TextInputRender = TextInput.render;
TextInput.render = function (...args) {
    const originText = TextRender.apply(this, args);
    const { style } = originText.props;
    return React.cloneElement(originText, {
        allowFontScaling: Platform.OS === 'android', // 只在iOS平台设置为false
    });
};复制代码

字体样式

国内市场不少手机均可以经过root的形式来设置系统字体样式。有些手机厂商(小米、华为等等)还提供了不须要root就能够直接设置系统字体样式功能。一样当咱们设置了手机系统的字体样式后,App中的字体也会改变。随之而来的多是UI布局错乱,界面不能统一。 以下图所示: 解决这种问题,咱们须要控制App中的字体样式不随系统样式改变便可。如何解决呢?设置自定义字体登场。 关于如何在React Native自定义字体我就很少赘述了。你们能够参考:Custom Font In React Native 咱们仍然能够采用设置全局属性来设置自定义字体,改变系统字体样式将不会影响App中的字体样式。

const TextRender = Text.render;
Text.render = function (...args) {
    const originText = TextRender.apply(this, args);
    const { style } = originText.props;
    return React.cloneElement(originText, {
        allowFontScaling: false,
        style: [{
            ...PlatformStyle({ fontFamily: 'PingFangRegular' }),
        }, style],
    });
};
 
 
 
/** * 根据平台设置对应样式 */
import { Platform } from 'react-native';
export default function platformStyle(androidStyle = {}, iosStyle = {}) {
    if (Platform.OS === 'android') {
        return androidStyle;
    }
    return iosStyle;
}复制代码

由于在iOS平台不会有字体样式的问题,因此咱们采用PlatformStyle区分设置。继续运行App,卧槽!!怎么有些字体又使用了系统字体。 以下图: 其实在 React Native 中当咱们为Text组件设置了 fontStyle 或者 fontWeight 属性后,就会出现该问题。卧槽,这就尴尬了,难道让我违背设计稿,粗体或者斜体字都不设置了?这确定不行,因此,咱们只能经过设置相应字体实现。为了统一,为们自定义Text组件:

import React, { Component } from 'react';
import { Text, StyleSheet, Platform } from 'react-native';
export default class TextWidget extends Component {
 
    renderAndroidText() {
 
        let { style, children } = this.props;
        let fontStyle = null;
        if (style) {
            if (style instanceof Array) {
                style = StyleSheet.flatten(style);
            }
            fontStyle = style.fontWeight ? {
                fontWeight: 'normal',
                fontFamily: 'PingFangBold',
            } : { fontFamily: 'PingFangRegular' };
        }
 
        return (
            <Text
                {...this.props}
                style={[ style, fontStyle ]}
            >
                {children}
            </Text>
        );
    }
 
    render() {
        return Platform.OS === 'ios' ? <Text {...this.props} /> : this.renderAndroidText();
    }
}复制代码

上面组件中,咱们经过判断是否又设置fontWeight属性,来选择对应的自定义字体。其实上面这样作仍是不够完善,若是咱们确实想用某种fontWeight 或者 fontStyle,该如何作呢?

能够根据以下规则进行设置:

fontWeight: 300: "Light", 400: "Regular", 700: "Bold", 900: "Black", normal: "Regular", fontStyle: bold: "Bold" italic: "Italic"

实现大体分为以下:

(1)经过检测Text设置的style属性中的fontWeight 和 fontStyle 获得对应的字体名称

(2)对 fontWeight 和 fontStyle属性进行过滤,确保不含有两个属性的设置,不然将无效

Text 属性检测

// getFontFamily.js
 
// 包含全部字体
const fonts = {
  SonglcyFont: {
    fontWeights: {
      300: "Light",
      400: "Regular",
      700: "Bold",
      900: "Black",
      normal: "Regular",
      bold: "Bold"
    },
    fontStyles: {
      normal: "",
      italic: "Italic"
    }
  },
};
 
const getFontFamily = (baseFontFamily, styles = {}) => {
  const { fontWeight, fontStyle } = styles;
  const font = fonts[baseFontFamily];
  const weight = fontWeight
    ? font.fontWeights[fontWeight]
    : font.fontWeights.normal;
  const style = fontStyle
    ? font.fontStyles[fontStyle]
    : font.fontStyles.normal;
 
  if (style === font.fontStyles.italic && weight === font.fontWeights.normal) {
    return `${baseFontFamily}-${style}`;
  }
 
  return `${baseFontFamily}-${weight}${style}`;
};
 
export default getFontFamily;复制代码

上面咱们定义了一个 getFontFamily 文件,在文件中,首先咱们 fonts 常量,在 fonts 中 声明项目中用到的全部字体配置。每一个字体下又包含 fontWeight 所对应的字体,例如,300 即对应了SonglcyFont字体下的Light形式的字体。以此类推。在 getFontFamily 方法中,baseFontFamily 为字体名,styles即Text组件的style。接着在该方法中,经过字体名拿到fonts对应的字体属性,而后判断Text组件的style中是否包含fontWeight,若是包含则取出对应的字体名,不包含则采用Regular字体样式。fontStyle与此相同。最后返回对应字体名称。例如:SonglcyFont-Light 最终自定义Text组件以下:

import React from "react";
import { Text, StyleSheet } from "react-native";
import getFontFamily from "./getFontFamily";
 
// 过滤 fontWeight fontStyle 属性, 生成新的 style 对象
const omit = (obj, keys) => {
  return Object.keys(obj)
    .reduce((result, key) => {
      if (!keys.includes(key)) {
        result[key] = obj[key];
      }
 
      return result;
    }, {});
};
 
const AppText = ({style, ...props}) => {
  // Text style
  const resolvedStyle = StyleSheet.flatten(style);
  // 经过对 Text style 的检测,拿到对应自定义字体
  const fontFamily = getFontFamily(resolvedStyle.fontFamily, resolvedStyle);
  // 过滤掉 Text style 中的 fontWeight fontStyle 获得新的 style 对象
  const newStyle = omit({...resolvedStyle, fontFamily},["fontStyle", "fontWeight"]);
 
  return (
    <Text {...props} style={newStyle} /> ); }; export default AppText;复制代码

上面自定义Text组件中,咱们作了两件事:

(1)经过对 Text style 的检测,拿到对应自定义字体
(2)过滤掉 Text style 中的 fontWeight fontStyle 获得新的 style 对象

终于能够愉快的玩耍啦~

总结

本篇对 React Native 中的字体问题作了总结性的方案概述。从最初的简单问题抛出,一步步实现最终的解决方案。但愿对你有所帮助。

相关文章
相关标签/搜索