expo-fontsを使うとアプリでカスタムフォントを利用することができます。
ただ、fontWeight
で太さを変えたい場合に一つのfontFamily
指定ではうまくいきません。例えば以下のようにカスタムフォントを指定した場合です。
useFonts({ NotoSansJP: require("./assets/fonts/NotoSansJP-Regular.otf") })
<Text style={{ fontFamily: "NotoSansJP", fontWeight: "normal" }}>
Normal
</Text>
<Text style={{ fontFamily: "NotoSansJP", fontWeight: "bold" }}>
Bold
</Text>
Androidの場合はフォントファイルの末尾が_bold
、_italic
、_bold_italic
になっていれば自動で読み込んでくれるため、bold用のフォントファイルを配置しておけばboldに変わってくれるのですが、iOSの場合はboldになってくれません。また、Androidの場合でもweight
を"100"
〜"900"
で細かく指定することはできません。
How to add fonts for different font weights for react-native android project? - Stack Overflow
ではどうしたらweightに応じて太さを変えるかですが、以下のIssueで対処法があったためこれを参考にしてweightを指定可能にしてみたいと思います。
Props font-weight doesn't work on iOS by use expo-font and custom font · Issue #9149 · expo/expo
NotoSansJPを利用すると仮定して進めます。
フォントのロード時にすべてのフォントファイルを個別のfontFamilyとしてロードします。
useFonts({
NotoSansJPRegular: require("./assets/fonts/NotoSansJP-Regular.otf"),
NotoSansJPBold: require("./assets/fonts/NotoSansJP-Bold.otf"),
NotoSansJPBlack: require("./assets/fonts/NotoSansJP-Black.otf"),
NotoSansJPThin: require("./assets/fonts/NotoSansJP-Thin.otf"),
NotoSansJPMedium: require("./assets/fonts/NotoSansJP-Medium.otf"),
NotoSansJPLight: require("./assets/fonts/NotoSansJP-Light.otf"),
})
プロジェクトに一つText
コンポーネントを用意します。各画面からはReact NativeのText
ではなくこのコンポーネントを常に使うようにします。
import { ComponentProps } from "react"
import { StyleProp, StyleSheet, Text as RnText, TextStyle } from "react-native"
export function Text(props: ComponentProps<typeof RnText>) {
return <RnText {...props} style={withFontFamilyStyle(props.style)} />
}
const FONT_MAPPING: { [key in TextStyle["fontWeight"]]: string } = {
"100": "NotoSansJPThin",
"200": "NotoSansJPLight",
"300": "NotoSansJPLight",
"400": "NotoSansJPRegular",
"500": "NotoSansJPMedium",
"600": "NotoSansJPMedium",
"700": "NotoSansJPBold",
"800": "NotoSansJPBlack",
"900": "NotoSansJPBlack",
normal: "NotoSansJPRegular",
bold: "NotoSansJPBold",
}
function withFontFamilyStyle(
style: StyleProp<TextStyle>,
): StyleProp<TextStyle> {
if (style == null) return style
const flattened = StyleSheet.flatten(style)
const fontWeight = flattened?.fontWeight
if (fontWeight == null) return style
const fontFamily = FONT_MAPPING[fontWeight]
if (fontFamily == null) return style
return [style, { fontFamily }]
}
withFontFamilyStyle()
でスタイルの中身をチェックしfontWeight
の値に応じてfontFamily
を指定したスタイルを付加します。
StyleSheet.flatten(style)
は、スタイルが配列の形になっていたり入れ子になってたりする場合に対応するため必要です。例えばスタイルが[{}, [{}, {}]]
のような形式になっていた場合でも、一つのオブジェクトにしてくれます。また、[{ fontWeight: "bold" }, { .fontWeight: "normal" }]
のように、後から追加されたスタイルで打ち消されている場合も、後から指定したものを優先してくれます。
それでは以下のようにそれぞれのfontWeightの値を指定して表示してみます。
import { Text } from "./Text"
{[
"100",
"200",
"300",
"400",
"500",
"600",
"700",
"800",
"900",
"normal",
"bold",
].map(weight => (
<Text
key={weight}
style={{ fontSize: 16, fontWeight: weight }}
>
{weight}
</Text>
))}
fontWeightに応じて太さが変わって表示されるようになりました。