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 中的字體問題作了總結性的方案概述。從最初的簡單問題拋出,一步步實現最終的解決方案。但願對你有所幫助。

相關文章
相關標籤/搜索