鸿蒙跨平台实战day47:React Native在OpenHarmony上的Font自定义字体注册详解

鸿蒙跨平台实战:React Native在OpenHarmony上的Font自定义字体注册详解

发布时间 :2026年2月24日
技术栈 :React Native + OpenHarmony 6.0.0 (API 20)
难度 :⭐⭐⭐☆☆
核心痛点 :字体不显示、图标字体失效、多端样式不一致

欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net


一、前言

在移动应用开发中,字体 不仅是信息的载体,更是品牌视觉识别的核心要素。然而,当我们将 React Native 应用迁移到 OpenHarmony 平台时,往往会遇到一个棘手的问题:自定义字体无法显示

很多开发者习惯了 Android/iOS 的"放置即用"模式,直接将 .ttf 文件放入 assets 目录即可生效。但在 OpenHarmony 上,这套逻辑完全行不通。OpenHarmony 采用了更严格、更安全的字体管理机制,要求开发者必须显式注册字体。

本文将深入剖析 React Native 在 OpenHarmony 上的字体注册全流程,从原理到实战,彻底解决字体缺失问题,并涵盖 IconFont 图标字体的特殊处理方案。

为什么 OpenHarmony 如此特殊?

平台 字体加载机制 是否需要注册 配置文件
Android 自动扫描 assets/fonts ❌ 否
iOS Info.plist 声明 + Bundle资源 ⚠️ 半自动 Info.plist
OpenHarmony 显式注册 + 资源声明 font.json + ArkTS代码

二、核心原理:OpenHarmony 字体加载机制

在 OpenHarmony 中,字体加载遵循以下严格流程


准备字体文件 .ttf/.otf
放入 resources/base/media 目录
配置 resources/base/profile/font.json
ArkTS 侧显式调用 loadFontSync/loadFont
React Native 侧使用 fontFamily
字体是否显示?
✅ 成功
❌ 检查注册名称一致性

关键点

  1. 资源声明 :必须在 font.json 中声明字体家族名称与文件路径的映射。
  2. 代码注册 :必须在 ArkTS 入口文件(如 Index.ets)中调用系统 API 进行注册。
  3. 名称一致 :JS 侧使用的 fontFamily 必须与 font.json 中定义的 familyName 完全一致(区分大小写)。

三、实战步骤:从零配置自定义字体

3.1 步骤一:准备字体文件

下载所需的字体文件(支持 .ttf.otf 格式),例如:

  • HarmonyOS_Sans.ttf (鸿蒙 Sans 字体)
  • MyBrand-Bold.otf (品牌定制字体)
  • iconfont.ttf (图标字体)

3.2 步骤二:放置字体资源

将字体文件放入 OpenHarmony 工程的资源目录:

bash 复制代码
# 项目结构
your-rn-project/
└── harmony/
    └── entry/
        └── src/
            └── main/
                └── resources/
                    └── base/
                        ├── media/          # 存放字体文件
                        │   ├── HarmonyOS_Sans.ttf
                        │   ├── MyBrand-Bold.otf
                        │   └── iconfont.ttf
                        └── profile/        # 存放配置文件
                            └── font.json

注意 :OpenHarmony 要求字体文件必须放在 resources/base/media 目录下,而不是 assets

3.3 步骤三:配置 font.json

resources/base/profile/font.json 中声明字体家族:

json 复制代码
{
  "fonts": [
    {
      "familyName": "HarmonyOS_Sans",
      "fontFile": "$media:HarmonyOS_Sans.ttf"
    },
    {
      "familyName": "MyBrand-Bold",
      "fontFile": "$media:MyBrand-Bold.otf"
    },
    {
      "familyName": "iconfont",
      "fontFile": "$media:iconfont.ttf"
    }
  ]
}

关键参数说明

  • familyName:字体家族名称,将在 React Native 的 fontFamily 样式中使用
  • fontFile:字体文件路径,格式为 $media:文件名.扩展名

3.4 步骤四:ArkTS 侧显式注册

这是最关键且最容易被忽略的一步 。必须在 ArkTS 代码中调用 fontManager.loadFontSync() 进行注册。

编辑 harmony/entry/src/main/ets/entryability/Index.ets(或对应的入口文件):

typescript 复制代码
// harmony/entry/src/main/ets/entryability/Index.ets
import { fontManager } from '@ohos.typography.font';
import { resourceManager } from '@ohos.resourceManager';
import { common } from '@ohos.app.ability.common';

export async function registerCustomFonts(context: common.UIAbilityContext) {
  try {
    // 获取资源管理器
    const rm = context.resourceManager;
    
    // 同步注册字体(推荐用于启动时)
    // 参数1: 字体家族名称(必须与font.json中的familyName一致)
    // 参数2: 资源路径ID
    fontManager.loadFontSync('HarmonyOS_Sans', rm.getRawFileContent('media/HarmonyOS_Sans.ttf'));
    fontManager.loadFontSync('MyBrand-Bold', rm.getRawFileContent('media/MyBrand-Bold.otf'));
    fontManager.loadFontSync('iconfont', rm.getRawFileContent('media/iconfont.ttf'));
    
    console.info('Custom fonts registered successfully');
  } catch (error) {
    console.error('Failed to register custom fonts:', error);
  }
}

// 在 Ability 的 onWindowStageCreate 中调用
@Entry
@Component
struct Index {
  @State message: string = 'Hello World';
  private abilityContext: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;

  aboutToAppear() {
    // 注册自定义字体
    registerCustomFonts(this.abilityContext);
  }

  build() {
    // ... 其他代码
  }
}

⚠️ 重要提示

  • 必须在 React Native 桥接初始化之前完成字体注册。
  • 推荐使用 loadFontSync 确保字体在 UI 渲染前已就绪。
  • 如果字体文件较大,可考虑异步 loadFont,但需处理加载状态。

3.5 步骤五:React Native 侧使用字体

完成上述步骤后,即可在 React Native 代码中正常使用自定义字体:

tsx 复制代码
// App.tsx
import React from 'react';
import { Text, StyleSheet, View } from 'react-native';

const App = () => {
  return (
    <View style={styles.container}>
      {/* 使用鸿蒙 Sans 字体 */}
      <Text style={styles.sansText}>
        这是 HarmonyOS Sans 字体
      </Text>

      {/* 使用品牌定制字体 */}
      <Text style={styles.brandText}>
        这是品牌定制粗体字
      </Text>

      {/* 使用图标字体(见下文详解) */}
      <Text style={styles.iconText}>
        {'\ue600'}  {'\ue601'}  {'\ue602'}
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  sansText: {
    fontSize: 20,
    fontFamily: 'HarmonyOS_Sans', // 必须与 font.json 中的 familyName 一致
    marginBottom: 20,
  },
  brandText: {
    fontSize: 24,
    fontFamily: 'MyBrand-Bold', // 区分大小写
    fontWeight: 'bold',
    marginBottom: 20,
  },
  iconText: {
    fontSize: 30,
    fontFamily: 'iconfont', // 图标字体
    color: '#007AFF',
  },
});

export default App;

四、进阶场景:IconFont 图标字体专项处理

图标字体(IconFont)是自定义字体的特殊应用场景,在 OpenHarmony 上需要额外注意以下几点:

4.1 常见问题:图标不显示

现象:字体文件已配置,但图标显示为空白或方框。

根本原因

  1. fontFamily 名称不一致。
  2. 未在 ArkTS 侧注册。
  3. 字符编码错误(未使用正确的 Unicode 码点)。

4.2 完整解决方案

(1) 创建图标映射表
typescript 复制代码
// utils/IconFontMap.ts
export const IconFontMap: Record<string, string> = {
  home: '\ue600',
  user: '\ue601',
  setting: '\ue602',
  search: '\ue603',
  // 添加更多图标...
};

export type IconName = keyof typeof IconFontMap;
(2) 封装 IconFont 组件
tsx 复制代码
// components/IconFont.tsx
import React from 'react';
import { Text, TextStyle, StyleProp } from 'react-native';
import { IconFontMap, IconName } from '../utils/IconFontMap';

interface IconFontProps {
  name: IconName;
  size?: number;
  color?: string;
  style?: StyleProp<TextStyle>;
}

const IconFont: React.FC<IconFontProps> = ({ 
  name, 
  size = 24, 
  color = '#000', 
  style 
}) => {
  const iconCode = IconFontMap[name];
  
  if (!iconCode) {
    console.warn(`Icon "${name}" not found in IconFontMap`);
    return null;
  }

  return (
    <Text
      style={[
        {
          fontSize: size,
          color: color,
          fontFamily: 'iconfont', // 必须与 font.json 注册名称一致
          textAlign: 'center',
          lineHeight: size, // 确保图标垂直居中
        },
        style,
      ]}
    >
      {iconCode}
    </Text>
  );
};

export default IconFont;
(3) 使用示例
tsx 复制代码
// pages/HomePage.tsx
import React from 'react';
import { View, StyleSheet } from 'react-native';
import IconFont from '../components/IconFont';

const HomePage = () => {
  return (
    <View style={styles.tabBar}>
      <IconFont name="home" size={28} color="#007AFF" />
      <IconFont name="user" size={28} color="#666" />
      <IconFont name="setting" size={28} color="#666" />
    </View>
  );
};

const styles = StyleSheet.create({
  tabBar: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    paddingVertical: 10,
    borderTopWidth: 1,
    borderTopColor: '#eee',
  },
});

export default HomePage;

五、自动化脚本:简化字体注册流程

手动配置容易出错,我们可以编写脚本自动化这一过程。

5.1 创建字体注册脚本

javascript 复制代码
// scripts/register-fonts.js
const fs = require('fs');
const path = require('path');

const FONTS_DIR = path.join(__dirname, '../src/assets/fonts');
const HARMONY_MEDIA_DIR = path.join(__dirname, '../harmony/entry/src/main/resources/base/media');
const HARMONY_FONT_JSON = path.join(__dirname, '../harmony/entry/src/main/resources/base/profile/font.json');
const HARMONY_INDEX_ETS = path.join(__dirname, '../harmony/entry/src/main/ets/entryability/Index.ets');

// 1. 复制字体文件到 Harmony 资源目录
function copyFonts() {
  if (!fs.existsSync(HARMONY_MEDIA_DIR)) {
    fs.mkdirSync(HARMONY_MEDIA_DIR, { recursive: true });
  }

  const fontFiles = fs.readdirSync(FONTS_DIR).filter(f => f.endsWith('.ttf') || f.endsWith('.otf'));
  
  fontFiles.forEach(file => {
    const src = path.join(FONTS_DIR, file);
    const dest = path.join(HARMONY_MEDIA_DIR, file);
    fs.copyFileSync(src, dest);
    console.log(`✅ Copied: ${file}`);
  });

  return fontFiles;
}

// 2. 生成 font.json
function generateFontJson(fontFiles) {
  const fonts = fontFiles.map(file => {
    const familyName = path.basename(file, path.extname(file));
    return {
      familyName: familyName,
      fontFile: `$media:${file}`
    };
  });

  const config = { fonts };
  fs.writeFileSync(HARMONY_FONT_JSON, JSON.stringify(config, null, 2));
  console.log('✅ Generated font.json');
}

// 3. 生成 ArkTS 注册代码片段
function generateArkTSCode(fontFiles) {
  const registrations = fontFiles.map(file => {
    const familyName = path.basename(file, path.extname(file));
    return `    fontManager.loadFontSync('${familyName}', rm.getRawFileContent('media/${file}'));`;
  }).join('\n');

  const template = `
  // 自动生成的字体注册代码
  ${registrations}
  `;

  // 读取现有 Index.ets 并插入注册代码
  let content = fs.readFileSync(HARMONY_INDEX_ETS, 'utf-8');
  
  // 查找 aboutToAppear 方法并插入代码
  const regex = /(aboutToAppear$$\s*\{)/;
  if (regex.test(content)) {
    content = content.replace(regex, `$1\n${template}`);
    fs.writeFileSync(HARMONY_INDEX_ETS, content);
    console.log('✅ Updated Index.ets with font registration');
  } else {
    console.warn('⚠️ Could not find aboutToAppear method in Index.ets');
  }
}

// 主函数
function main() {
  console.log('🚀 Starting font registration for OpenHarmony...\n');
  
  const fontFiles = copyFonts();
  generateFontJson(fontFiles);
  generateArkTSCode(fontFiles);
  
  console.log('\n✅ Font registration completed!');
  console.log('📝 Remember to rebuild your Harmony project.');
}

main();

5.2 配置 package.json

json 复制代码
{
  "scripts": {
    "register-fonts": "node scripts/register-fonts.js",
    "build:harmony": "npm run register-fonts && npx react-native run-harmonyos"
  }
}

5.3 使用方式

bash 复制代码
# 将字体文件放入 src/assets/fonts 目录
# 然后运行
npm run register-fonts

# 或直接构建
npm run build:harmony

六、常见问题及解决方案

问题 可能原因 解决方案
字体完全不显示 未在 ArkTS 侧注册 检查 Index.ets 中是否调用 loadFontSync
部分字符显示为方框 字体文件不包含该字符集 更换包含完整字符集的字体文件
IconFont 图标不显示 fontFamily 名称不一致 确保 JS 侧与 font.json 中的 familyName 完全一致
热更新后字体失效 缓存问题 清理构建缓存:rm -rf harmony/build
字体加载慢 字体文件过大 使用字体子集化工具(如 fonttools)裁剪无用字符
多语言排版错乱 字体不支持特定语言 使用支持多语言的字体(如 Noto Sans CJK)

七、性能优化建议

7.1 字体子集化

对于仅用于图标的字体文件,建议使用工具裁剪掉不需要的字符,减小文件体积:

bash 复制代码
# 使用 fonttools 进行子集化
pip install fonttools
pyftsubset iconfont.ttf --text="ue600-ue6ff" --output-file=iconfont-subset.ttf

7.2 异步加载大字体

对于较大的中文字体文件,建议使用异步加载避免阻塞启动:

typescript 复制代码
// 异步加载示例
async function loadLargeFont(context: common.UIAbilityContext) {
  const rm = context.resourceManager;
  try {
    await fontManager.loadFont('MyLargeFont', rm.getRawFileContent('media/MyLargeFont.ttf'));
    console.info('Large font loaded asynchronously');
  } catch (error) {
    console.error('Failed to load large font:', error);
  }
}

7.3 预加载策略

在应用启动时预加载常用字体,避免首次渲染时的闪烁:

typescript 复制代码
// 在 AboutToAppear 中预加载
aboutToAppear() {
  registerCustomFonts(this.abilityContext);
  // 预加载其他字体...
}

八、完整代码下载

bash 复制代码
# GitHub 仓库
git clone https://github.com/harmonyos-rn/font-registration-demo.git

# 安装依赖
cd font-registration-demo
npm install

# 运行字体注册脚本
npm run register-fonts

# 构建并运行(HarmonyOS)
npm run build:harmony

九、总结

本文详细介绍了 React Native 在 OpenHarmony 平台上实现自定义字体注册 的完整方案,核心要点如下:

步骤 关键操作 注意事项
1. 资源放置 字体放入 resources/base/media 支持 .ttf/.otf
2. 配置声明 编辑 font.json familyName 将用于 JS 侧
3. 代码注册 ArkTS 调用 loadFontSync 必须在 RN 初始化前完成
4. JS 使用 fontFamily: '名称' 名称必须完全一致
5. 图标字体 封装组件 + Unicode 映射 注意字符编码

通过本教程,你可以:

  • ✅ 彻底解决 OpenHarmony 上字体不显示的问题
  • ✅ 正确配置 IconFont 图标字体
  • ✅ 实现自动化字体注册流程
  • ✅ 优化字体加载性能

十、参考资料


💡 提示:本文代码基于 OpenHarmony 6.0.0 (API 20) 和 React Native 0.72.5+,如有更新请以官方文档为准。
📢 欢迎交流:如有问题,欢迎在评论区留言讨论!


相关推荐
阿林来了2 小时前
Flutter三方库适配OpenHarmony【flutter_web_auth】— OpenHarmony 插件工程搭建与配置文件详解
flutter·harmonyos
早點睡3903 小时前
Flutter for Harmony 跨平台开发实战:鸿蒙与音乐律动艺术、FFT 频谱能量场:正弦函数的叠加艺术
flutter·华为·harmonyos
ChinaDragonDreamer3 小时前
HarmonyOS:知识点总结(一)
harmonyos·鸿蒙
张雨zy4 小时前
HarmonyOS鸿蒙 Preference 数据存储:简单实用的本地存储方案
华为·harmonyos
Cxiaomu4 小时前
React Native项目(Android )集成虹软 ArcFace(人脸识别增值版 5.0 Java)
android·java·react native
不爱吃糖的程序媛4 小时前
Flutter-OH 插件适配 HarmonyOS 实战:以屏幕方向控制为例
flutter·华为·harmonyos
松叶似针4 小时前
Flutter三方库适配OpenHarmony【doc_text】— 文件格式路由:.doc 与 .docx 的分流策略
flutter·harmonyos
阿林来了4 小时前
Flutter三方库适配OpenHarmony【flutter_web_auth】— FlutterPlugin 与 AbilityAware 双接口实现
flutter·harmonyos
星空22234 小时前
鸿蒙跨平台实战day49:React Native在OpenHarmony上的Font字体降级策略详解
react native·华为·harmonyos