移动开发中的自定义字体 — 概念、格式与应用

作者: IT Sectr 发布日期: 2026-07-23 阅读时间: 10 分钟

Custom Font — 是一个字体文件(TTF、OTF、WOFF2),开发者将其添加到移动应用中用以替换系统默认字体。自定义字体用于创建独特的视觉风格、符合品牌手册以及提高不同语言的阅读性。根据 Apple Fonts 的数据,自定义字体在所有 Apple 平台上均受支持,而 Android 提供了 Font Family 机制来简化加载。字体的正确集成对排版和应用的总体观感有着关键影响。

要点

  • Custom Font — 添加到应用中用于替换系统字体的字体文件
  • 主要格式:TTF(TrueType)、OTF(OpenType)、WOFF2(Web Open Font Format)
  • iOS 通过 Info.plist 和 UIFont 集成字体,Android — 通过 res/font 和 Typeface
  • 每个字体 会为 APK 或 IPA 增加 50 KB 到 5 MB 的大小
  • 可变字体 允许将所有字形存储在一个文件中

什么是 Custom Font?

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(可变字体)— 现代格式,将所有字形存储在一个文件中。

格式iOSAndroid压缩特点
TTF标准格式,广泛兼容性
OTFOpenType 功能,连字,替代字形
WOFF2通过 SafariAPI 29+30-50%压缩格式,节省 APK 大小
VariableiOS 11+API 29+一个文件包含所有字形

对于移动开发,建议使用 OTF 作为主要格式 — 它在与 TTF 相同的大小下支持 OpenType 功能。为节省 APK 空间,请使用 woff2 压缩或按需加载字体。

在 iOS 中集成自定义字体

在 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 条目。

swift
// 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)的处理是必须的。应使用系统字体作为 回退字体,而不是让应用崩溃。这对于从网络加载或添加到可选模块的字体尤其重要。

通过 CTFontManager 动态加载

对于按需下载的字体,使用 CTFontManagerRegisterGraphicsFont。此方法从内存中的数据(Data)注册字体并使其可用于 UIFont。使用后可通过 CTFontManagerUnregisterGraphicsFont 取消注册。动态加载适用于拥有大量字体且并非所有字体都同时需要的应用。

在 Android 中集成自定义字体

在 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) 时自行选择粗体。

创建 Font Family

在 res/font/my_font.xml 中创建一个 XML 文件,列出不同字形的所有字体文件。每个元素具有 fontStyle(normal/italic)和 fontWeight(100-900)属性。然后在标记中指定 android:fontFamily="@font/my_font"。使用 textStyle="bold" 时,Android 会自动选择 fontWeight 为 700 的文件。

xml
<!-- 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 和 React Native 中集成

在跨平台框架中,字体的集成是统一的。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 和主题允许全局设置字体。

Flutter 示例

在 pubspec.yaml 中声明字体并在 MaterialApp 主题中应用。文件 Montserrat-Regular.ttf 和 Montserrat-Bold.ttf 放置在 fonts/ 中。声明后,字体可在整个应用中通过 TextStyle 使用。Flutter 在 fontWeight: FontWeight.bold 时自动使用粗体文件。

dart
// 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。

kotlin
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 吗?

可以。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 加载字体。

如何减小 APK 中的字体大小?

使用 subsetting(pyftsubset)删除未使用的字符 — 可将大小减少 50-80%。使用可变字体替代 10 个单独的文件。对于 Android 10+ 使用 WOFF2 压缩。配置 ProGuard 以混淆字体路径。对于 iOS,使用按需资源。

一个应用可以使用多少种自定义字体?

建议限制在 2-3 个字体族(包括不同字形)。每个字体族为应用增加 200 KB 到 3 MB 的大小。系统字体不会增加应用负担,因此 UI 元素使用系统字体,自定义字体保留用于标题和强调块。

总结

  • Custom Font — 添加到应用中的字体文件(TTF、OTF、WOFF2),用于品牌排版
  • iOS:bundle 中的文件 + Info.plist 中的 UIAppFonts + UIFont(name:size:)
  • Android:res/font 中的文件 + XML Font Family + Typeface 或 android:fontFamily
  • Flutter:在 pubspec.yaml 中声明 + TextStyle(fontFamily:)
  • 优化:subsetting 可将字体大小减少 50-80%,同时保留必要字符
  • 可变字体 用一个包含所有字形的文件替代 10-20 个静态文件
  • 缓存 加载的字体 — 每个应用会话一个实例

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读