打造 Material 字体样式主题 | 实现篇

使用 Material 主题 (Theming) 自定义 Material 组件,目的是让组件观感与品牌保持一致。Material 主题包括 颜色字体形状 参数,您可以对这些参数进行调整来获得近乎无限的组件变体,同时保持其核心结构和易用性。

自版本 1.1.0 开始,您可以在 Android 中使用 Material 组件 (Material Design Components, MDC) 库 来实现 Material 主题。如果您要从设计支持库 (Design Support Library) 或 MDC 1.0.0 迁移至新版 MDC,请参阅我们提供的迁移指南—— 迁移至 Android Material 组件

本文将重点讨论如何实现字体样式主题。

字体样式属性

Material Design 提供 13 种适用于应用中所有文字的 "样式 (styles)",每一种样式都有一个设计术语 (例如 "Body 1") 以及对应的字体样式属性,您可以在应用主题中覆写这些属性 (例如 textAppearanceBody1)。每一种样式的属性都有默认的 "基准" 值 (文字尺寸、字符间距、大小写等)。

△ 具有基准值的 MDC 字体样式属性

Material 组件使用这些字体样式属性来为组件的文本元素设置样式,这些组件通常继承自 TextView 或组合了一个或多个 TextView。

△ 一个按钮中使用的字体样式属性 (红色)

字体样式属性在布局和组件样式中的应用如下:

android:textAppearance=”?attr/textAppearanceBody1”

关于字体样式属性的使用,以及多种样式化方案同时使用时被应用的优先级顺序,如需了解更多,请查阅 Nick Butcher 的文章 —— "如何实现文字外观"。

在 MDC 主题中,这些属性会映射到样式上,例如:

<style name=”Theme.MaterialComponents.*” parent="...">
   ...
   <item name=”textAppearanceBody1”>
       @style/TextAppearance.MaterialComponents.Body1
   </item>
<style />

您也许在 AppCompat 或平台中已经接触过 TextAppearance 样式,我们将在本文的 字体样式资源 部分进行详细介绍。其对应的属性是 MDC 的新增内容,使您能够根据不同主题变换不同文字样式。

选择字体样式

厘清应该选择使用何种字体样式以及其中的属性值也许是设计师的责任,也许它们源自您的品牌。然而,了解每一种样式的作用及其使用场景是非常有用的:

  • textAppearanceHeadline* 样式应用于标题

  • textAppearanceSubtitle* 样式应用于副标题

  • textAppearanceBody* 样式应用于多行文本正文

  • textAppearanceButton 样式应用于按钮,但是同样也适用于其他组件的部分内容,例如 Tab 和弹窗中的操作

  • textAppearanceCaption 样式应用于小号文本,例如输入框的提示和错误信息

  • textAppearanceOverline 样式也应用于小号文本,但是它具有大写英文字母和更大的字符间距,因此更适合于小标题和 Label,例如日期选择器的标题

字体样式工具

Material Design 提供了一个实用工具,它可以预览字型缩放,集成了 Google Font,并且可以导出代码。请查阅 Material Design 字体样式指南 中的 "字型缩放生成器"。

![△ Google Font (左) 和字型缩放生成器 (右)](https://devrel.andfun.cn/devrel/posts/2021/08/SCcneI.png

字体样式资源

字体样式资源由字体和 TextAppearance 样式组成。让我们来看看 Android 中可用的资源以及声明样式时的注意事项。

XML 和可下载字体

字体存放于 res/font 目录下,通过 @font/ 符号引用。您可以使用本地的 XML 字体 或者 可下载字体。Android Studio 内置了向导以帮助您开始使用可下载字体,包括配置必要的证书和清单元数据。请查阅由 Rod Sheeter 撰写的 "助力 Android 开发者实现更好的排版指南" 来了解关于字体预加载更详细的指南和进一步的优化。

我们通常推荐使用可下载字体,因为它们会借助共享字体提供程序的缓存来减小应用包体积。但是,可下载字体目前仅可使用 Google Font 上的字体。如果您的应用需要使用已购买的字体或专用字体,请使用 XML 字体。

同样值得注意的是,从 API 26 开始,Android 支持使用可变字体。请查阅 Rebecca Franks 的文章 —— "Android O 上的可变字体🖍" 以了解更多信息。

TextAppearance 样式

TextAppearance 样式可以被当成是 Android 上的 Material Design 字体样式。对于自定义的样式,我们推荐两种方法来帮您实现关注点分离,并为应用中的字体样式主题值创建单一的数据来源:

  • 将所有 TextAppearance 样式存放在同一个 res/values/type.xml 文件中

  • 使用 MDC TextAppearance 作为父样式,并遵守相同的命名规则

这些样式中可使用的属性和值与 TextView 支持的属性和值一致:

  • fontFamily 定义字族,通常使用 @font/ 资源引用 XML 或可下载字体

  • android:textSize 定义文本的大小,通常是一个 sp 尺寸

  • android:textColor 定义文本的颜色

  • android:letterSpacing 定义字符的间距

  • android:textAllCaps 定义是否开启文本大写,是一个布尔值

  • android:textFontWeight 定义字体的粗细,用于从字族中选择最接近的匹配项,但是只在 API 28 及以上的版本中可用。也可以使用 android:textStyle 来设置效果,例如 bold (粗体) 和 italic (斜体)

<!-- Copyright 2020 Google LLC.
   SPDX-License-Identifier: Apache-2.0 -->

<!-- In res/values/type.xml -->
<style name="TextAppearance.App.Headline6" parent="TextAppearance.MaterialComponents.Headline6">
    <item name="fontFamily">@font/roboto_mono</item>
    ...
</style>
<style name="TextAppearance.App.Body2" parent="TextAppearance.MaterialComponents.Body2">
    <item name="fontFamily">@font/roboto_mono</item>
    <item name="android:textSize">14sp</item>
    ...
</style>
<style name="TextAppearance.App.Button" parent="TextAppearance.MaterialComponents.Button">
    <item name="fontFamily">@font/roboto_mono</item>
    <item name="android:textAllCaps">false</item>
    ...
</style>

计算字符间距

字符间距在 Android 中使用的测量单位 (em) 与设计工具如 Sketch 使用的测量单位 (tracking) 不同。Material Design 排版指南 提供了一个相对简单的方程式将 tracking 值转换为合适的 em 值:

(Sketch 中的 tracking 值 / 字体尺寸 sp) = 字符间距

<!-- Copyright 2020 Google LLC.
   SPDX-License-Identifier: Apache-2.0 -->

<!-- (0.25 tracking / 14sp font size) = 0.0178571429 em -->
<style name="TextAppearance.App.Body2" parent="TextAppearance.MaterialComponents.Body2">
    <item name="fontFamily">@font/roboto_mono</item>
    <item name="android:textSize">14sp</item>
+    <item name="android:letterSpacing">0.0178571429</item>
    ...
</style>

MaterialTextView 和行高

系统版本的 TextView 在 API 28 中添加了 android:lineHeight 属性。MDC 通过 MaterialTextView 类为该属性提供了向下兼容能力。您不需要直接在布局中使用该类,因为 MaterialComponentsViewInflater 会自动将 <TextView> 替换为 MaterialTextView

您可以在多种场景中使用 lineHeight:

  • 作为一个 item 被包含于 TextAppearance 样式中 (使用 android:textAppearance="..." 应用该样式)
  • 作为一个 item 被包含于父样式为 Widget.MaterialComponents.TextView 的组件样式中 (使用 style="..." 应用该样式)
  • 直接应用于布局中的 <TextView>
△ 不同的行高值

注意事项

  • 您不必覆写全部字体样式。但是请注意,默认的 MDC 样式使用系统字体 (通常是 Roboto)。请确保检查了您的组件和 TextView 使用的是哪种字体样式。
  • 虽然 TextAppearance 支持设置 android:textColor,但 MDC 偏向于在主要组件样式中声明该属性以保证遵循关注点分离原则,例如:
<style name=”Widget.MaterialComponents.*” parent=”...”>
   ...
   <!-- Color -->
   <item name=”android:textColor”>?attr/colorOnSurface</item>
   <!-- Type -->
   <item name=”android:textAppearance”>
       ?attr/textAppearanceBody1
   </item>
</style>

额外的字体样式

如果您的设计系统需要的字体样式在 Material 主题提供的 13 种样式外,庆幸的是在 Android 中实现起来相对简单,您可以通过如下方式声明样式属性:

<!-- Copyright 2020 Google LLC.
   SPDX-License-Identifier: Apache-2.0 -->

<!-- In res/values/attrs.xml -->
<attr name="textAppearanceCustom" format="reference" />

<!-- In res/values/type.xml -->
<style name="TextAppearance.App.Custom" parent="TextAppearance.MaterialComponents.*">
    ...
</style>

<!-- In res/values/themes.xml -->
<style name="Theme.App" parent="Theme.MaterialComponents.*">
    ...
    <item name="textAppearanceCustom">@style/TextAppearance.App.Custom</item>
</style>

覆写应用主题中的字体样式

接下来,我们来讨论如何通过覆写相应属性,将您选择的字体样式添加到应用主题中。

首先,我们建议您设置主题以便优雅地处理浅色和深色调色板,同时也可以减少与基本主题的重复。如需了解更多此话题相关信息,请参阅 Chris Banes 撰写的 深色主题文章,以及他和 Nick Butcher 的演讲 —— "使用样式开发主题"。

设置完成后,在您应用的基本主题中覆写您想要改变的字体样式属性:

<!-- Copyright 2020 Google LLC.
   SPDX-License-Identifier: Apache-2.0 -->

<!-- In res/values/themes.xml -->
<style name="Theme.App.Base" parent="Theme.MaterialComponents.*">
    ...
    <item name="textAppearanceHeadline6">
        @style/TextAppearance.App.Headline6
    </item>
    <item name="textAppearanceBody2">
        @style/TextAppearance.App.Body2
    </item>
    <item name="textAppearanceButton">
        @style/TextAppearance.App.Button
    </item>
    <!-- Using default values for textAppearanceSubtitle1, textAppearanceCaption, etc. -->
</style>

Material 组件会响应主题级的字体样式覆写:

△ Material 组件响应主题级的字体样式覆写

MDC 组件中的字体样式

您已经知道 MDC 组件会响应主题级的样式覆写。但是您如何知道诸如某个按钮使用 textAppearanceButton 作为它文本标签的样式呢?让我们来看看以下几种方式。

构建 Material 主题

构建 Material 主题 是一个可交互的 Android 项目,您可以通过它修改颜色、字体样式、形状的值来创建您自己的 Material 主题。它还包含了所有主题参数和组件的目录。您可以按如下步骤来确定哪些组件会响应主题字体样式属性的改变:

△ 构建 Material 主题中的字体样式变化

MDC 开发者文档

MDC 开发者文档已于最近更新。在本次更新中,我们加入了属性表,涵盖了开发库中所使用的设计术语和属性默认值。例如下面是更新的 按钮文档 的 "Anatomy and key properties" (详解和关键属性) 部分。

△ MDC 按钮开发者文档中属性表包含了字体样式的默认值

源码

检索 MDC 源码可以说是最可靠的方式。MDC 使用默认样式来实现 Material 主题,因此可以查看这些样式以及任何可样式化属性和 Java 文件。例如,查阅 MaterialButton 的 样式属性Java 文件

△ MDC 按钮默认样式中使用的字体样式

自定义 View 中的字体样式

您的应用中也许会引入您自己开发或现有库中的自定义组件。当它们与标准 MDC 组件共同使用时,有必要保证它们能响应 Material 主题变化。以下是为自定义组件支持样式主题化的注意事项。

在 <declare-styleable> 和默认样式中使用 MDC 属性

当自定义 View 使用了<declare-styleable> 标签时将可被样式化。复用 MDC 中的 attr name 有利于保持统一。使用 <declare-styleable> 标签的默认样式同样可以引用 MDC 主题样式的属性作为它们的值。

<!-- Copyright 2020 Google LLC.
   SPDX-License-Identifier: Apache-2.0 -->

<!-- In res/values/attrs.xml -->
<declare-styleable name="AppCustomView">
    <attr name="titleTextAppearance" />
    <attr name="subtitleTextAppearance" />
    ...
</declare-styleable>

<!-- In res/values/styles.xml -->
<style name="Widget.App.CustomView" parent="android:Widget">
    <item name="titleTextAppearance">?attr/textAppearanceHeadline6</item>
    <item name="subtitleTextAppearance">?attr/textAppearanceBody2</item>
    ...
</style>

下一步

我们已经在 Android 应用中实现了 MDC 字体样式主题。有关 Material 主题的其他课题,请阅读该系列其他文章。

我们一如既往地期待您在 GitHub 上提交 错误报告功能需求。另外请务必查看 Android 示例应用

欢迎您与我们 分享 您实现的字体样式主题。如果您遇到任何问题,请 点击这里 向我们提交反馈。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容