Custom Font — 是一个字体文件(TTF、OTF、WOFF2),开发者将其添加到移动应用中用以替换系统默认字体。自定义字体用于创建独特的视觉风格、符合品牌手册以及提高不同语言的阅读性。根据 Apple Fonts 的数据,自定义字体在所有 Apple 平台上均受支持,而 Android 提供了 Font Family 机制来简化加载。字体的正确集成对排版和应用的总体观感有着关键影响。
要点
Custom Font(自定义字体)— 是一个不属于操作系统标准字体集的字体文件。开发者将此类字体添加到项目中以实现符合品牌的独特排版。自定义字体可以是付费商业字体,也可以是免费开源字体,例如 Inter、Montserrat、Roboto(自定义版本)。
自定义字体的典型使用场景:品牌应用的标志和标题、具有特殊排版要求的阅读应用、使用主题字体的游戏、系统字体不支持所需字形的非拉丁语言应用。根据 Google Fonts 的数据,前 100 名应用中超过 60% 的应用会自定义字体以实现视觉差异化。
系统字体(iOS 上的 San Francisco,Android 上的 Roboto)针对界面进行了优化,但不适合品牌排版。自定义字体 增强品牌辨识度,可以包含特殊符号(图标字体),并提高特定受众的阅读性。然而,过多使用不同字体反而会降低用户体验 — 建议每个应用不超过 2-3 个字体族。
移动平台支持多种字体格式。TrueType(TTF) — 最广泛的格式,兼容所有版本的 iOS 和 Android。OpenType(OTF)— TTF 的扩展,支持连字、替代字形和 OpenType 功能。两种格式的扩展名分别为 .ttf 和 .otf,在移动平台上运行方式相同。
WOFF2(Web Open Font Format 2)— 为网络优化的压缩格式,从 Android 10 开始支持。WOFF2 相比 TTF 提供 30-50% 的压缩率。在 iOS 上,WOFF2 通过 Safari 支持,但在原生应用中需要转换为 TTF/OTF。Variable Fonts(可变字体)— 现代格式,将所有字形存储在一个文件中。
| 格式 | iOS | Android | 压缩 | 特点 |
|---|---|---|---|---|
| TTF | 是 | 是 | 否 | 标准格式,广泛兼容性 |
| OTF | 是 | 是 | 否 | OpenType 功能,连字,替代字形 |
| WOFF2 | 通过 Safari | API 29+ | 30-50% | 压缩格式,节省 APK 大小 |
| Variable | iOS 11+ | API 29+ | 高 | 一个文件包含所有字形 |
对于移动开发,建议使用 OTF 作为主要格式 — 它在与 TTF 相同的大小下支持 OpenType 功能。为节省 APK 空间,请使用 woff2 压缩或按需加载字体。
在 iOS 中集成自定义字体的过程包括两个步骤:将文件添加到项目 bundle 并在 Info.plist 中注册。字体文件放置在项目目录(通常为 Resources/Fonts)中并包含在 target 中。在 Info.plist 中添加 UIAppFonts(Fonts provided by application)数组,包含字体文件名及扩展名。
注册后,字体可通过 UIFont(name:size:) 以其 PostScript 名称使用。通过 UIFont.familyNames 和 UIFont.fontNames(forFamilyName:) 可获取 PostScript 名称。如果名称不正确,UIFont(name:size:) 返回 nil。建议在首次启动时在屏幕上显示所有已注册字体以进行调试。
我们来看在 Swift 编写的 iOS 项目中集成 Montserrat 字体的完整流程。文件 Montserrat-Regular.ttf 添加到项目中,在 Info.plist 中指定,然后通过 UIFont 加载并应用到 UILabel。每种字形(regular、bold、italic)添加单独的文件和单独的 Info.plist 条目。
// 1. 在 Info.plist 中:UIAppFonts → "Montserrat-Regular.ttf"
// 2. 在代码中加载字体
guard let customFont = UIFont(name: "Montserrat-Regular", size: 16) else {
// 回退到系统字体
label.font = UIFont.systemFont(ofSize: 16)
return
}
label.font = customFont
// 3. 在 NSAttributedString 中使用
let attributes = [NSAttributedString.Key.font: customFont]
let attributed = NSAttributedString(string: "Text", attributes: attributes)
当字体未加载时(UIFont 返回 nil)的处理是必须的。应使用系统字体作为 回退字体,而不是让应用崩溃。这对于从网络加载或添加到可选模块的字体尤其重要。
对于按需下载的字体,使用 CTFontManagerRegisterGraphicsFont。此方法从内存中的数据(Data)注册字体并使其可用于 UIFont。使用后可通过 CTFontManagerUnregisterGraphicsFont 取消注册。动态加载适用于拥有大量字体且并非所有字体都同时需要的应用。
在 Android 中,自定义字体通过 res/font 资源系统添加。TTF 或 OTF 文件放置在 res/font/ 中,然后创建 XML Font Family 资源来对字形进行分组。从 Android 8.0(API 26)开始,这是唯一推荐的方式。对于旧版本,使用 assets 和 Typeface.createFromAsset。
XML Font Family 描述了字形(regular、bold、italic)与字体文件之间的对应关系。在 XML 标记中,属性 android:fontFamily="@font/my_font" 在使用对应样式调用 setTypeface 时自动选择合适的文件。这简化了排版 — 只需在主题中指定字体族,Android 就会在调用 setTypeface(textView, Typeface.BOLD) 时自行选择粗体。
在 res/font/my_font.xml 中创建一个 XML 文件,列出不同字形的所有字体文件。每个元素具有 fontStyle(normal/italic)和 fontWeight(100-900)属性。然后在标记中指定 android:fontFamily="@font/my_font"。使用 textStyle="bold" 时,Android 会自动选择 fontWeight 为 700 的文件。
<!-- res/font/my_font.xml -->
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto">
<font
android:fontStyle="normal"
android:fontWeight="400"
app:font="@font/my_font_regular" />
<font
android:fontStyle="normal"
android:fontWeight="700"
app:font="@font/my_font_bold" />
</font-family>
使用 Font Family 时,资源方式会自动缓存字体并选择正确的字形。如果所需字重在家族中不存在,Android 会从最近的可写字重合成。要对字体进行精确控制,请使用 Typeface.create() 并显式指定文件。
在跨平台框架中,字体的集成是统一的。Flutter 使用 pubspec.yaml 来指定字体:文件放置在项目的 fonts/ 文件夹中,然后在 fonts 部分中声明字体族和字形。之后通过 TextStyle(fontFamily: 'Montserrat') 或在应用主题中应用字体。Flutter 支持 TTF 和 OTF。
React Native 使用两种方式:原生方式(通过 Info.plist 和 res/font)或通过 react-native-vector-icons 和 @expo-google-fonts 库。Expo 简化了流程 — 字体通过 expo-font 加载:Font.loadAsync({ 'Montserrat': require('./assets/fonts/Montserrat.ttf') })。React Navigation 和主题允许全局设置字体。
在 pubspec.yaml 中声明字体并在 MaterialApp 主题中应用。文件 Montserrat-Regular.ttf 和 Montserrat-Bold.ttf 放置在 fonts/ 中。声明后,字体可在整个应用中通过 TextStyle 使用。Flutter 在 fontWeight: FontWeight.bold 时自动使用粗体文件。
// pubspec.yaml
flutter:
fonts:
- family: Montserrat
fonts:
- asset: fonts/Montserrat-Regular.ttf
- asset: fonts/Montserrat-Bold.ttf
weight: 700
// 在主题中使用
MaterialApp(
theme: ThemeData(
textTheme: TextTheme(
headlineLarge: TextStyle(
fontFamily: 'Montserrat',
fontWeight: FontWeight.bold,
fontSize: 28
)
)
)
)
如果 Flutter 在指定路径上找不到字体,应用将无错误地使用 系统回退字体。要调试,请在 MaterialApp 中启用 checkConsistency — 它会检查字体可用性并在缺少时在控制台中显示警告。
APK 和 IPA 的大小直接取决于所包含字体的数量。一个 TTF 文件对于拉丁字符集约 50-500 KB,对于支持西里尔字母、汉字或其他扩展集的字体可达 2-5 MB。十个字体可能为应用增加 10-20 MB 的大小,这对移动下载来说是关键的。
优化时使用 subsetting — 从字体文件中删除未使用的字符。像 glyphhanger、fonttools(pyftsubset)和 Google Webfont Optimizer 这样的工具可以创建仅包含必要字形(拉丁字母 + 西里尔字母 + 数字 + 标点符号)的字体版本。这样可以将大小减少 50-80%。在 iOS 上,可以为首次启动时不需要的字体使用按需资源。
Variable Font(可变字体)— 一个文件包含所有字形:从 Thin 到 Black,从 Condensed 到 Expanded。一个可变字体可以替代 10-20 个不同字重的单独文件。这样一个文件的大小大约相当于 1-2 个静态文件。支持情况:iOS 11+、Android 10+、Flutter(通过 FontVariation)、React Native(通过自定义库)。
使用 WOFF2 可将字体大小减少 30-50%。在 API 29 的 Android 上可直接使用 WOFF2。在 iOS 上,注册前需将 WOFF2 转换为 TTF。仅用于引导流程或特定屏幕的字体,请通过按需资源(iOS)或 Dynamic Feature(Android)加载 — 它们不会进入基础 APK。
自定义字体通过两种方式影响性能:加载时间和内存。首次引用字体时,系统从磁盘读取文件,解析字体表并创建内部结构用于渲染。对于 1 MB 大小的字体,这需要 20-50 毫秒。首次加载后的缓存可消除重复引用时的延迟。
内存:每个加载的字体存储在进程缓存中。系统字体已预加载,自定义字体在首次使用时加载。由于解析后的结构(cmap、glyf、head、hmtx 表),字体在内存中占用的空间约为磁盘上的 2-3 倍。对于 5 个中等大小的自定义字体(总容量 3 MB),大约需要 6-9 MB 的 RAM。
将自定义字体数量限制为每个应用 2-3 个字体族。使用可变字体替代多个字形。对 UI 元素(按钮、标签、列表)使用系统字体,仅对强调排版(标题、横幅)使用自定义字体。在 Android 上通过 Map 缓存 Typeface,在 iOS 上通过静态变量缓存 UIFont。
object FontCache {
private val cache = mutableMapOf<String, Typeface>()
fun get(context: Context, fontId: Int): Typeface {
return cache.getOrPut(fontId.toString()) {
ResourcesCompat.getFont(context, fontId)
}
}
}
// 在 RecyclerView 中使用
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.textView.typeface = FontCache.get(context, R.font.montserrat_regular)
}
在 onBindViewHolder 中加载 Typeface 仅在存在缓存时允许。没有缓存时,每次调用 createFromAsset 都会创建新对象并从磁盘读取文件。滚动 RecyclerView 时会导致明显的卡顿。缓存解决了这个问题,因为字体只加载一次并重复使用。
常见问题
OTF — 移动应用的最佳选择。它在与 TTF 相同的大小下支持 OpenType 功能(连字、替代字形)。为节省空间,请使用带有 subsetting 的 TTX 或 OTF 可变字体。WOFF2 仅适用于 Android 10+,且不适用于 iOS 原生应用。
可以。Google Fonts 在 OFL(开源字体许可证)下提供字体供商业使用。字体可以从 fonts.google.com 下载并添加到项目中。Android 通过 Google Play Services 支持 可下载字体,这使得无需将字体文件包含在 APK 中,而是在首次使用时加载。
最常见的原因:文件未添加到 target(iOS)、未在 Info.plist 中指定(iOS)、文件不在 res/font 中(Android)、指定了错误的 PostScript 名称、文件损坏。在 iOS 上调试时,在控制台中输出 UIFont.familyNames。在 Android 上,使用 FontLoader 调试从 assets 加载字体。
使用 subsetting(pyftsubset)删除未使用的字符 — 可将大小减少 50-80%。使用可变字体替代 10 个单独的文件。对于 Android 10+ 使用 WOFF2 压缩。配置 ProGuard 以混淆字体路径。对于 iOS,使用按需资源。
建议限制在 2-3 个字体族(包括不同字形)。每个字体族为应用增加 200 KB 到 3 MB 的大小。系统字体不会增加应用负担,因此 UI 元素使用系统字体,自定义字体保留用于标题和强调块。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。