React Native Android如何程序化获取默认font-family并实现自定义字体适配
我最近在React Native(包括Expo)Android项目里正好解决了这个需求——获取系统默认字体并做适配,分享一下具体实现步骤,分为获取系统字体和适配逻辑两部分:
获取Android系统默认font-family
React Native本身没有提供直接获取系统默认字体的API,所以得靠原生模块来实现,核心是调用Android原生的Settings类读取字体配置。
非Expo裸项目实现
创建Android原生模块
在你的Android项目src/main/java/com/[你的项目包名]/下新建SystemFontModule.java:package com.yourprojectname; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.Callback; import android.provider.Settings; public class SystemFontModule extends ReactContextBaseJavaModule { public SystemFontModule(ReactApplicationContext reactContext) { super(reactContext); } @Override public String getName() { // 模块名称,JS端会用到 return "SystemFontModule"; } @ReactMethod public void getSystemFontFamily(Callback callback) { try { ReactApplicationContext context = getReactApplicationContext(); // 获取系统当前设置的字体家族 String systemFont = Settings.System.getString(context.getContentResolver(), Settings.System.FONT_FAMILY); // 兜底:如果返回null,说明是系统默认未修改的状态,返回Android标准默认字体标识 callback.invoke(null, systemFont != null ? systemFont : "sans-serif"); } catch (Exception e) { callback.invoke(e.getMessage(), null); } } }注册模块
新建SystemFontPackage.java:package com.yourprojectname; import com.facebook.react.ReactPackage; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ViewManager; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class SystemFontPackage implements ReactPackage { @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { List<NativeModule> modules = new ArrayList<>(); modules.add(new SystemFontModule(reactContext)); return modules; } @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); } }然后在
MainApplication.java的getPackages()方法里添加这个package:@Override protected List<ReactPackage> getPackages() { @SuppressWarnings("UnnecessaryLocalVariable") List<ReactPackage> packages = new PackageList(this).getPackages(); // 添加自定义模块包 packages.add(new SystemFontPackage()); return packages; }JS端调用
import { NativeModules } from 'react-native'; const { SystemFontModule } = NativeModules; // 封装成异步函数 export const getSystemFontFamily = async () => { try { return await new Promise((resolve, reject) => { SystemFontModule.getSystemFontFamily((error, result) => { if (error) reject(error); else resolve(result); }); }); } catch (err) { console.error('获取系统字体失败:', err); // 兜底返回标准字体 return "sans-serif"; } };
Expo项目实现
Expo不能直接修改原生代码,需要用Expo自定义模块结合expo-dev-client实现:
- 用
npx create-expo-module expo-system-font创建自定义模块,在模块的Android代码里实现和上面裸项目一样的逻辑 - 在项目中安装模块并运行
npx expo prebuild生成原生项目 - JS端调用方式和裸项目一致,直接导入模块调用即可
适配逻辑:自定义字体 vs 系统标准字体
核心思路是:判断系统返回的字体是否为Android标准默认值(sans-serif,它会自动映射到系统内置的Roboto或其他默认无衬线字体),如果不是,说明用户修改了系统字体,切换到自定义字体;否则用系统标准字体。
1. 配置自定义字体
裸项目:把字体文件放在
assets/fonts目录,在react-native.config.js中配置:module.exports = { assets: ['./assets/fonts/'], };然后运行
npx react-native link链接字体。Expo项目:在
app.json中配置:{ "expo": { "fonts": [ { "asset": "./assets/fonts/YourCustomFont-Regular.ttf", "fontFamily": "YourCustomFont" } ] } }用
expo-font的useFonts钩子加载字体:import { useFonts } from 'expo-font'; import { ActivityIndicator } from 'react-native'; export default function App() { const [fontsLoaded] = useFonts({ YourCustomFont: require('./assets/fonts/YourCustomFont-Regular.ttf'), }); if (!fontsLoaded) { return <ActivityIndicator />; } return <YourAppRootComponent />; }
2. 实现适配逻辑的自定义Text组件
import { useEffect, useState } from 'react'; import { Text, StyleSheet, DeviceEventEmitter } from 'react-native'; import { getSystemFontFamily } from './path-to-your-function'; export default function AdaptiveText({ children, style }) { const [fontFamily, setFontFamily] = useState('sans-serif'); useEffect(() => { // 初始化获取系统字体 const initFontCheck = async () => { const systemFont = await getSystemFontFamily(); updateFontFamily(systemFont); }; // 更新字体逻辑 const updateFontFamily = (systemFont) => { setFontFamily(systemFont !== 'sans-serif' ? 'YourCustomFont' : 'sans-serif'); }; initFontCheck(); // 可选:监听系统字体实时变化 const fontChangeSubscription = DeviceEventEmitter.addListener( 'fontFamilyChanged', updateFontFamily ); // 清理监听 return () => fontChangeSubscription.remove(); }, []); return ( <Text style={[styles.baseText, { fontFamily }, style]}> {children} </Text> ); } const styles = StyleSheet.create({ baseText: { fontSize: 16, color: '#333', }, });
补充:实时监听系统字体变化
如果需要实时响应用户修改系统字体的操作,在之前的原生模块里添加ContentObserver监听字体变化事件:
在SystemFontModule.java中添加:
import com.facebook.react.modules.core.DeviceEventManagerModule; import android.os.Handler; import android.database.ContentObserver; private ContentObserver fontObserver; @Override public void initialize() { super.initialize(); ReactApplicationContext context = getReactApplicationContext(); fontObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { super.onChange(selfChange); try { String newFont = Settings.System.getString(context.getContentResolver(), Settings.System.FONT_FAMILY); // 发送事件到JS端 context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit("fontFamilyChanged", newFont != null ? newFont : "sans-serif"); } catch (Exception e) { e.printStackTrace(); } } }; // 注册监听 context.getContentResolver().registerContentObserver( Settings.System.getUriFor(Settings.System.FONT_FAMILY), false, fontObserver ); } @Override public void onCatalystInstanceDestroy() { super.onCatalystInstanceDestroy(); ReactApplicationContext context = getReactApplicationContext(); if (fontObserver != null) { // 销毁监听 context.getContentResolver().unregisterContentObserver(fontObserver); } }
内容的提问来源于stack exchange,提问作者Vishal Avalani




