作者 / Nick Rout,Material 开发技术推广工程师
Material 主题 (theming) 是一种自定义 Material 组件的方式,目的是使其与品牌保持一致。Material 主题涉及包括颜色、排版和形状,您可以对它们进行调整来获得近乎无限的组件变体,且依然保持其核心结构和易用性。
Material 主题
https://material.io/design/material-theming/overview.html#material-theming
Material 组件
https://material.io/components
颜色
https://material.io/design/color/
排版
https://material.io/design/typography/
形状
https://material.io/design/shape/
自版本 1.1.0 开始,您可在 Android 上通过 Material Components (MDC) 库实现 Material 主题。如果您要从设计支持库 (Design Support Library) 或 MDC 1.0.0 迁移,请查看我们的迁移指南。
MDC 库
https://github.com/material-components/material-components-android
迁移指南
https://material.io/blog/migrate-android-material-components
本文将重点讨论如何实现形状主题。
大多数 widget 都有一个背景形状,但您有没有思考过形状对用户行为的影响?就像颜色和排版一样,形状可以引导用户的注意力,展示交互性,并在视觉上区分界面中的元素。Material 的形状主题可以定义全局形状值,从而改变整个应用中组件的样式。例如,让所有的卡片、对话框和菜单都呈现出打磨过的圆角。
形状属性
Material Design 提供了 3 个形状 "类别",适用于应用中大大小小有形的 widget。每个类别都有一个设计术语 (例如 "小型组件"),以及相应的可以在您的应用主题中进行自定义的形状属性 (例如 shapeAppearanceSmallComponent)。每个类别都有默认的 "基准" 值 (角尺寸、角形状等)。
△ MDC 形状属性与基准值
Material 组件使用这些形状属性来设置 widget 背景的样式。
△ 一个按钮所用的形状属性 (红色)
它们以如下形式:
app:shapeAppearance=”?attr/shapeAppearanceSmallComponent”
应用于布局和 widget 样式中。
在 MDC 主题中,这些属性会映射到样式,如:
ShapeAppearance 叠加层
您还可以定义 ShapeAppearance 叠加层,它支持所有相同的属性,行为上也类似于主题叠加层。
它们可以通过 app:shapeAppearanceOverlay 与常规 ShapeAppearance 样式一起应用,以更改特定某个角的属性值。以下是底部菜单叠加层的示例 (来自 MDC 源代码),将菜单底部两个角的半径设为和屏幕的角半径相同:
注: 某些 MDC widget 默认已应用叠加层,您在调整其 shapeAppearance 时可能需要考虑这些叠加层。例如 FloatingActionButton 和 Chip,它们都通过叠加层将 cornerSize 设置为 50%。
FloatingActionButton
https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/floatingactionbutton/FloatingActionButton.java
Clip
https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/chip/Chip.java
填充和描边
与 XML 可绘制对象不同,ShapeAppearance 样式不包括任何填充或描边的概念。MDC 倾向于在主 widget 样式中单独指定这些值来减少耦合:
注: ShapeAppearance 样式和背后的 MaterialShapeDrawable 类仅支持纯色填充和描边。目前尚不支持渐变,您需要将 XML 可绘制对象与
在应用主题中覆盖形状
接下来我们来看如何通过覆盖相关属性将您选择的形状添加到应用主题中。
首先,您的主题需要妥善处理浅色和深色调色板,同时减少与基础主题的重复。有关这方面的更多信息,请查看 Chris Banes 关于深色主题的文章,以及他和 Nick Butcher 的 "如何正确开发外观样式" 演讲。
设置完成后,在基础主题中覆盖要更改的形状属性:
Material Design 组件将响应主题级别的形状覆盖:
△ Material Design 组件响应主题级别的形状覆盖
MaterialShapeDrawable
形状主题由 MaterialShapeDrawable 类驱动。它是所有 MDC widget 默认的背景可绘制对象,并用于呈现形状。与其他可绘制对象不同,它无法在 XML 中使用,需要以编程方式处理。
MaterialShapeDrawable
https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/shape/MaterialShapeDrawable.java
△ MaterialShapeDrawable 和 ShapeAppearanceModel 图示
MaterialShapeDrawable 可以这样实例化:
// Default constructor
val msd = MaterialShapeDrawable()
// ShapeAppearanceModel constructor
val msdFromSam = MaterialShapeDrawable(shapeAppearanceModel)
// Style/attr resources constructor (reads shapeAppearance and shapeAppearanceOverlay)
val msdFromStyles = MaterialShapeDrawable(context, attrs, defStyleAttr, defStyleRes)
// Cast from widget background
val msdFromWidget = widget.background as MaterialShapeDrawable
ShapeAppearanceModel
ShapeAppearanceModel 是 ShapeAppearance 样式的程序化等效项,它存储形状的倒角和边线的数据。MaterialShapeDrawable 则使用此类渲染其形状。
ShapeAppearanceModel
https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/shape/ShapeAppearanceModel.java
生成器模式用于实例化 ShapeAppearanceModel:
// Default builder
val sam = ShapeAppearanceModel.builder()
.setAllCorners(CornerFamily.CUT, cornerSize)
// Also setTopRightCorner, setAllEdges, etc.
.build()
// Style/attr resources builder (reads shapeAppearance and shapeAppearanceOverlay)
val samFromStyles = ShapeAppearanceModel.builder(context, attrs, defStyleAttr, defStyleRes)
.build()
// Build from existing ShapeAppearanceModel
val samFromExisting = sam.toBuilder()
...
.build()
有关边线和自定义路径更高级的示例,请参阅 MDC 目录中的 BottomAppBarCutCornersTopEdge。
BottomAppBarCutCornersTopEdge
https://github.com/material-components/material-components-android/blob/master/catalog/java/io/material/catalog/bottomappbar/BottomAppBarCutCornersTopEdge.java
填充和描边
MaterialShapeDrawable 处理填充和描边的渲染。有许多方法可以调整这些属性:
// Fill color
msd.setFillColor(fillColorStateList)
// Stroke color
msd.setStrokeColor(strokeColorStateList)
// Stroke width
msd.setStrokeWidth(strokeWidthDimension)
高程和叠加层
MaterialShapeDrawable 负责渲染叠加层以呈现深色主题中的高程 (深色主题时不使用阴影,而是使用明暗表示高程),这些操作由 MDC widget 默认处理。启用和使用此功能的方法如下:
// Initialize elevation overlays
msd.initializeElevationOverlay(context)
// Pass elevation value to MSD to apply overlay (in dark theme)
msd.setElevation(elevation)
如需了解更多信息,请参阅之前发布的《打造 Material 颜色主题 | 实现篇》以及 Chris Banes 关于深色主题的文章。
阴影渲染
平台只在 API 21 及更高级别中支持高程阴影的渲染。MaterialShapeDrawable 为向后移植阴影渲染提供了可能性:
/**
* Set shadow compat mode to be one of:
* - SHADOW_COMPAT_MODE_DEFAULT: Use platform rendering on API 21+, else compat rendering
* - SHADOW_COMPAT_MODE_NEVER: Use platform rendering always
* - SHADOW_COMPAT_MODE_ALWAYS: Use compay rendering always
*/
msd.setShadowCompatibilityMode(shadowMode)
角插值
MaterialShapeDrawable 提供了所有角尺寸的插值方法,通过提供从 0.0~1.0 之间取值的乘数,方便开发者在动画和转场中使用。
// Set corner interpolation to half of current cornerSize(s)
msd.setInterpolation(0.5f)
了解 MDC widget 中的形状
如前所述,MDC widget 会响应主题级别覆盖的形状属性。但是,举例来说,怎样才能知道一个按钮会使用 shapeAppearanceSmallComponent 作为其容器的样式?让我们来看看几种学习途径。
使用 "构建 Material 主题" 项目
构建 Material 主题是一个交互式 Android 项目,您可以自定义颜色、排版和形状来创建自己的 Material 主题。项目里还包含了所有主题的参数和组件的目录。要确定哪些 widget 响应主题中属性的变化,可以这样操作:
复制项目并在 Android Studio 中运行应用
修改 res/values/shape.xml 以及 res/values/themes.xml 中的值
重新运行应用并观察界面中发生的变化
构建 Material 主题
https://github.com/material-components/material-components-android-examples/tree/develop/MaterialThemeBuilder
Shape.xml
https://github.com/material-components/material-components-android-examples/blob/develop/MaterialThemeBuilder/app/src/main/res/values/shape.xml
Themes.xml
https://github.com/material-components/material-components-android-examples/blob/develop/MaterialThemeBuilder/app/src/main/res/values/themes.xml
△ 构建 Material 主题中形状值的变化
阅读 MDC 开发者文档
我们最近更新了 MDC 开发者文档,加入了属性表,其中给出了库中所使用的设计术语和默认值。例如,下图是按钮文档的 "Anatomy and key properties"(详解和关键属性) 部分。
按钮文档
https://material.io/components/buttons/android
△ MDC 开发者文档中按钮的形状属性表以及默认值
阅读源代码
阅读 MDC 源代码可谓是最可靠的方法。MDC 使用默认样式实现 Material 主题,因此看一看这些样式以及所有可设置的属性和 java 源文件是个不错的办法。例如,查看 MaterialButton 的 styles、attrs 和 java 源文件。
Material Button 的源文件
https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/button/res/values/styles.xml
https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/button/res/values/attrs.xml
https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/button/MaterialButton.java
一个有趣的做法是观察 MDC widget 如何使用默认样式来确保 MaterialShapeDrawable 为默认背景。一般的做法是:
在 widget 默认样式中将 android:background 设置为 @null 或 @empty
如果在解析属性时未检测到背景,则以编程方式实例化一个 MaterialShapeDrawable 并将其设置为背景
如果已设置背景 (例如在布局或自定义样式中),则予以保留,并不再使用 MaterialShapeDrawable
△ MDC 按钮默认样式以及取值
自定义视图中的形状
您的应用可能包含自己构建或从现有库中获得的自定义 widget。在和标准 MDC widget 进行混用时,让这些 widget 支持 Material 主题非常有用。我们来看看让自定义 widget 支持形状主题时要注意些什么。
在
通过使用
...
注意高程和叠加层
如果您希望自定义视图支持高程叠加层或向后移植阴影渲染,最好覆盖 setElevation 方法并将其值传递至 MaterialShapeDrawable 背景:
class AppCustomView ... {
...
private lateinit var materialShapeDrawable: MaterialShapeDrawable
override fun setElevation(elevation: Float) {
super.setElevation(elevation)
materialShapeDrawable.setElevation(elevation)
}
}
下一步
现在,我们已经在 Android 应用中实现了 MDC 形状主题。有关 Material 主题的其他课题,请阅读我们相关的介绍文章。
为什么推荐使用 MDC
https://medium.com/androiddevelopers/we-recommend-material-design-components-81e6d165c2dd
排版主题
https://material.io/blog/android-material-theme-type
颜色主题
https://material.io/blog/android-material-theme-color
深色主题
动效系统
https://material.io/blog/android-material-motion
我们一如既往地期待您在 GitHub 上提交错误报告和功能需求。另外,请务必查看 Android 组件示例应用。
提交错误报告
https://github.com/material-components/material-components-android/issues/new?assignees=&labels=bug&template=bug_report.md&title=%5BComponent+name%5D+Short+description+of+issue
提交功能需求
https://github.com/material-components/material-components-android/issues/new?assignees=&labels=feature+request&template=feature_request.md&title=%5BComponent+name%5D+Short+description+of+request
Android 组件示例应用
https://github.com/material-components/material-components-android-examples
如果您已成功实现形状主题,或您在实现期间遇到问题,欢迎在下方评论区和我们分享。
推荐阅读
点击屏末 | 阅读原文 | 查看 Material Design 设计指南