You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

React Native Android如何程序化获取默认font-family并实现自定义字体适配

我最近在React Native(包括Expo)Android项目里正好解决了这个需求——获取系统默认字体并做适配,分享一下具体实现步骤,分为获取系统字体和适配逻辑两部分:

获取Android系统默认font-family

React Native本身没有提供直接获取系统默认字体的API,所以得靠原生模块来实现,核心是调用Android原生的Settings类读取字体配置。

非Expo裸项目实现

  1. 创建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);
        }
      }
    }
    
  2. 注册模块
    新建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.javagetPackages()方法里添加这个package:

    @Override
    protected List<ReactPackage> getPackages() {
      @SuppressWarnings("UnnecessaryLocalVariable")
      List<ReactPackage> packages = new PackageList(this).getPackages();
      // 添加自定义模块包
      packages.add(new SystemFontPackage());
      return packages;
    }
    
  3. 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实现:

  1. npx create-expo-module expo-system-font创建自定义模块,在模块的Android代码里实现和上面裸项目一样的逻辑
  2. 在项目中安装模块并运行npx expo prebuild生成原生项目
  3. 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-fontuseFonts钩子加载字体:

    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

火山引擎 最新活动