在 Android 上可使用 1.1.0 版本及以上的 Material 组件 (Material Design Components, MDC) 库来实现 Material 主题。如果您希望从 Design Support Library 或 MDC 1.0.0 迁移到新版本的 MDC,请参阅我们的迁移指南。
颜色属性
Material Design 提供可供填充的 12 个颜色 "槽 (slots)",这些色值构成应用的整体调色板。每个 "槽" 都有一个设计术语 (如 Primary),该术语则对应一个可在应用主题中覆盖的颜色属性 (如 Primary 这个术语对应 colorPrimary 这个颜色属性)。这些是您的浅色和深色主题默认的基准色值。
△ 浅色主题的基准 MDC 颜色属性
△ 深色主题的基准 MDC 颜色属性
Material 组件使用这些颜色属性为各个 widget 着色。
app:backgroundTint=”?attr/colorSecondary”
您可能会认得下表中的一些颜色属性,如 colorPrimary。这些因为一些颜色属性继承自 AppCompat 和平台本身,而其余属性则来由 MDC。每个颜色属性的完整来源见下表。
挑选颜色
颜色工具
△ Material 颜色工具 (左) 和 Material 调色板生成器 (右)
注意事项
其他颜色槽
除 Material 主题指定的 12 个颜色槽以外,您的设计系统可能还会用到其他颜色槽。幸运的是,在 Android 上声明颜色属性的操作很简单:
<!-- In res/values/attrs.xml -->
<attr name="colorCustom" format="color" />
<!-- In res/values/themes.xml -->
<style name="Theme.App" parent="Theme.MaterialComponents.*">
...
<item name="colorCustom">@color/...</item>
</style>
颜色资源
<color> 色值使用字面名称命名 (而不是基于使用方式命名):
<!-- In res/values/colors.xml -->
<color name="navy_500">#64869B</color>
<color name="navy_700">#37596D</color>
<color name="navy_900">#073042</color>
<color name="green_300">#3DDC84</color>
<color name="green_500">#00A956</color>
覆盖应用主题中的颜色
让我们了解一下如何通过覆盖相关属性将您自己的调色板应用到主题中。
首先,您的主题需要在妥善处理浅色和深色调色板的同时减少与基础主题的重复。有关这方面的更多信息,请参阅 Chris Banes 关于深色主题的文章以及 Chris Banes 和 Nick Butcher 的 "如何正确开发外观样式" 演讲。
设置好主题后,覆盖您希望在浅色和深色主题中更改的颜色属性即可:
<!-- In res/values/themes.xml -->
<style name="Theme.App" parent="Theme.App.Base">
...
<item name="colorPrimary">@color/navy_700</item>
<item name="colorPrimaryVariant">@color/navy_900</item>
<item name="colorSecondary">@color/green_300</item>
<item name="colorSecondaryVariant">@color/green_900</item>
<!-- Using default values for colorOnPrimary, colorSurface, colorError, etc. -->
</style>
<!-- In res/values-night/themes.xml -->
<style name="Theme.App" parent="Theme.App.Base">
...
<item name="colorPrimary">@color/navy_500</item>
<item name="colorPrimaryVariant">@color/navy_900</item>
<item name="colorSecondary">@color/green_300</item>
<item name="colorSecondaryVariant">@color/green_300</item>
<!-- Using default values for colorOnPrimary, colorSurface, colorError, etc. -->
</style>
Material 组件将根据主题全局应用您覆盖好的颜色:
颜色复用性和最佳实践
在许多情况下,都要在布局、可绘制对象、样式和其他位置使用颜色。我们将介绍一些可尽量复用代码的方法,而且不会影响您在应用主题中指定的色值。
<Button
...
- app:backgroundTint="@color/green_300"
+ app:backgroundTint="?attr/colorPrimary"
- android:textColor="@color/black"
+ android:textColor="?attr/colorOnPrimary"
/>
请参阅 Nick Butcher 的《Android 样式系统 | 主题背景属性》,以了解更多说明和此规则的一些例外情况。
有时,您可能希望使用 MDC 主题中的一种颜色,但带上 alpha 值 (例如 60% 的 colorPrimary)。例如,触发点击时的波纹动画和项目被选中的状态。
Android <color> 资源支持 alpha 通道:
<!-- 60% alpha = 99 -->
<color name=”navy_700_alpha_60”>#9937596D</color>
<!-- In res/color/primary_60.xml -->
<selector ...>
<item android:alpha="0.6" android:color="?attr/colorPrimary" />
</selector>
每种状态的颜色和主题叠加
更为常见的情况是根据视图状态使用 ColorStateList 切换颜色 (和 alpha 值)。MDC widget 大量将其用于禁用状态、悬停状态和按压状态等。比如下面是一个 MDC 按钮的背景色源代码:
<!-- In button/res/color/mtrl_btn_bg_color_selector.xml -->
<selector ...>
<item android:color="?attr/colorPrimary" android:state_enabled="true" />
<item android:alpha="0.12" android:color="?attr/colorOnSurface" />
</selector>
和上面这个按钮的示例类似,假设您希望将主背景色从主色改为辅色:
您当然可以将以上源文件复制一份,然后将 colorPrimary 改为 colorSecondary,但如果源代码恰巧发生更改,那么此操作会很繁琐且会出现问题。
一种更好的方式是使用主题叠加。Nick Butcher 在他的《Android 样式系统 | 主题背景覆盖》一文中对此有详细的介绍。基本上,我们可以替换 View 或 ViewGroup 的主题属性值 (在我们例子中为 colorPrimary) 以及所有依赖它的项目 (在我们的例子中为按钮)。
下面是一个主题叠加的简单例子。请注意 parent 为空,其作用是确保我们仅覆盖希望更改的属性:
<!-- In res/values/themes.xml -->
<style name="ThemeOverlay.App.PrimarySecondary" parent="">
<item name="colorPrimary">?attr/colorSecondary</item>
<item name="colorOnPrimary">?attr/colorOnSecondary</item>
</style>
<Button
...
<!-- Alternatively apply with android:theme -->
+ app:materialThemeOverlay="@style/ThemeOverlay.App.PrimarySecondary"
/>
API 兼容性
平台是在 API 23 才开始在 CSL 等处增加对 ?attr/ 语法的支持。如果您的 minSdk 是更早的版本,也不要担心: 有兼容性类!事实上,MDC 和 AppCompat widget 都有在底层使用这些兼容性类,因此在使用时无需其他操作。
对于需要以编程方式使用 CSL 的场景,请使用 AppCompatResources:
val primary60 = AppCompatResources#getColorStateList(
context, R.color.primary60
)
MDC widget 中的颜色
之前我们曾说过,MDC widget 会响应主题级别的颜色属性覆盖。但是,您是如何知道按钮会将 colorPrimary 用作其背景着色并将 colorOnPrimary 用于其图标和文本呢?让我们来看一下几个选项。
△ 构建 Material 主题中的色值变化
源代码
查看 MDC 源代码是了解其工作机制最可靠的方法。MDC 使用默认样式实现 Material 主题,因此看一看这些样式以及所有可设置样式的属性和 java 文件是个不错的办法。比如您可以去看看 MaterialButton 的样式、属性和 java 源文件。
△ MDC 按钮的默认样式和色值
自定义视图中的颜色
您的应用可能包含您自己构建或从现有库中获得的自定义 widget。在和 MDC 一起使用时,使这些视图对 Material 主题作出响应非常必要。让我们看一看在将颜色主题用于自定义 widget 时需要牢记的事项。
为自定义视图设置样式需要使用 <declare-styleable>。在保持一致性方面,复用 MDC 中的属性名称非常有用。使用 <declare-styleable> 的默认样式还可以使用 MDC 主题颜色属性:
<!-- In res/values/attrs.xml -->
<declare-styleable name="AppCustomView">
<attr name="backgroundTint" />
<attr name="titleTextColor" />
...
</declare-styleable>
<!-- In res/values/styles.xml -->
<style name="Widget.App.CustomView" parent="android:Widget">
<item name="backgroundTint">?attr/colorSurface</item>
<item name="titleTextColor">
@color/material_on_surface_emphasis_high_type
</item>
...
</style>
可以通过便利的新 MDC 类 (MaterialColors) 以编程方式处理主题的颜色属性,这对于自定义视图也非常有用:
// Resolve color from theme attr
val primaryColor = MaterialColors.getColor(
view, R.attr.colorPrimary
)
// Layer background color with overlay color + alpha
val overlayedColor = MaterialColors.layer(
view, R.attr.colorSurface, R.attr.colorPrimary, 0.38f
)
下一步
现在,我们已经在 Android 应用中使用 MDC 实现了颜色主题。有关 Material 主题的其他课题,请阅读我们相关的介绍文章。
我们一如既往地期待您在 GitHub 上提交错误报告和功能需求。另外,请务必查看 Android 组件示例应用。
如果您已成功实现颜色主题或您在实现期间遇到问题,欢迎在下方评论区和我们分享。
推荐阅读