鸿蒙跨平台实战: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
字体是否显示?
✅ 成功
❌ 检查注册名称一致性
关键点:
- 资源声明 :必须在
font.json中声明字体家族名称与文件路径的映射。 - 代码注册 :必须在 ArkTS 入口文件(如
Index.ets)中调用系统 API 进行注册。 - 名称一致 :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 常见问题:图标不显示
现象:字体文件已配置,但图标显示为空白或方框。
根本原因:
fontFamily名称不一致。- 未在 ArkTS 侧注册。
- 字符编码错误(未使用正确的 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 字体管理官方文档
- React Native for OpenHarmony GitHub
- OpenHarmony 三方库中心仓
- fonttools 字体子集化工具
- Google Fonts 开源字体库
💡 提示:本文代码基于 OpenHarmony 6.0.0 (API 20) 和 React Native 0.72.5+,如有更新请以官方文档为准。
📢 欢迎交流:如有问题,欢迎在评论区留言讨论!