原标题:打造 Material 形状主题 | 实现篇
作者 / 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 主题中,这些属性会映射到样式,如:
< stylename= ”Theme.MaterialComponents.*”parent= ”...”>
...
< itemname= ”shapeAppearanceMediumComponent”>
@style/ShapeAppearance.MaterialComponents.MediumComponent
item>
< style/>
ShapeAppearance样式和对应属性对于 MDC 是新增内容,将在本文接下来的 "形状资源" 部分详细介绍。
选择形状
主题中使用的形状类别以及其中的参数取值可能会由设计师负责给出,也可能取自您的产品品牌。不过,了解每个形状类别的作用以及使用场景还是很有用的:
shapeAppearanceSmallComponent 用于按钮和文本字段等小尺寸组件
shapeAppearanceMediumComponent 用于卡片和对话框等中尺寸组件
shapeAppearanceLargeComponent 用于底部菜单等大尺寸组件
请参阅 形状设计指南,了解每个组件对应的形状类别。
形状设计指南
https://material.io/design/shape/applying-shape-to-ui.html#shape-scheme
形状工具
Material Design 提供了一个实用的形状自定义工具,用于预览形状类别以及参数被如何应用到各种组件的倒角上。
形状自定义工具
https://material.io/design/shape/about-shape.html#shape-customization-tool
△ 形状自定义工具
形状资源
形状资源主要由ShapeAppearance样式组成。这类似于排版主题中的TextAppearance样式;在本文的上下文里,"样式" 仅关注形状属性。我们来看看 Android 与 MDC 上有哪些可用的样式,以及在声明样式时需要注意的几个问题。
XML 形状和 android:background
在 MDC 之前,您通常需要在 res/drawable 目录下定义一个自定义背景,例如:
< shapeandroid:shape= "rectangle">
…
< cornersandroid:radius= "8dp"/>
< solidandroid:color= "?attr/colorSurface"/>
shape>
它应用于这样的 widget:
< View
…
android:background= ”@drawable/shape_background”/>
这是一个简化示例。XML 形状可绘制对象可以包括许多其他元素,例如 、 、 等,或支持多种状态。
虽然有些时候确实有必要使用这种方法,但仍有一些缺点值得注意:
它缺乏其他主题系统的许多实用功能 (如颜色和字体),指定主题级别形状的预定义属性,叠加层以及从样式中抽象出形状值的能力
Material Design 的形状系统 支持圆角和切角,但通过 XML 或编程方式并没有足够好的解决方案可以实现切角
难以处理复杂形状,如无法实现 底部应用栏 顶部的倒角,并且需要实现一个自定义的 Drawable
Material Design 形状系统
https://material.io/design/shape/about-shape.html#shaping-material
底部应用栏
https://material.io/components/app-bars-bottom
ShapeAppearance 样式
MDC 提供了一种定义形状的新方法。 ShapeAppearance样式可被视为 Material Design 形状在 Android 系统中的等效项。这些样式提供了一种无需直接处理可绘制对象即可定义形状特征的方法。目前仅适用于 MDC widget,并由一个新的 MaterialShapeDrawable类支持,下文会有详细介绍。
定义形状主题时,我们有两种推荐方法来进行解耦,并为您应用中的形状参数创建单一数据来源:
在一个 res/values/shape.xml 文件中保存所有 ShapeAppearance 样式
将 MDC ShapeAppearance 样式用作父级并遵循相同的命名规则
您可以在这些样式中使用的属性和值与 MaterialShapeDrawable支持的一致:
cornerFamily 是所有角的形状,分为 "圆角" 和 "切角"
cornerFamilyTopLeft 、 cornerFamilyTopRight 、 cornerFamilyBottomLeft 和 cornerFamilyBottomRight 允许您更改特定角的形状,并且优先于 cornerFamily
cornerSize 是所有角的尺寸,通常为 dp 尺寸
cornerSizeTopLeft 、 cornerSizeTopRight 、 cornerSizeBottomLeft 和 cornerSizeBottomRight 允许您更改特定角的尺寸,并且优先于 cornerSize
< stylename= "ShapeAppearance.App.SmallComponent"parent= "ShapeAppearance.MaterialComponents.SmallComponent">
< itemname= "cornerFamily">cut item>
< itemname= "cornerSize">4dp item>
...
style>
< stylename= "ShapeAppearance.App.MediumComponent"parent= "ShapeAppearance.MaterialComponents.MediumComponent">
< itemname= "cornerFamily">cut item>
< itemname= "cornerSize">6dp item>
...
style>
< stylename= "ShapeAppearance.App.LargeComponent"parent= "ShapeAppearance.MaterialComponents.LargeComponent">
< itemname= "cornerFamily">cut item>
< itemname= "cornerSize">0dp item>
...
style>
ShapeAppearance 叠加层
您还可以定义 ShapeAppearance叠加层,它支持所有相同的属性,行为上也类似于主题叠加层。
它们可以通过 app:shapeAppearanceOverlay与常规 ShapeAppearance样式一起应用,以更改特定某个角的属性值。以下是底部菜单叠加层的示例 (来自 MDC 源代码),将菜单底部两个角的半径设为和屏幕的角半径相同:
< stylename= "Widget.MaterialComponents.BottomSheet"parent= "...">
...
< itemname= "shapeAppearance">?attr/shapeAppearanceLargeComponent item>
< itemname= "shapeAppearanceOverlay">@style/ShapeAppearanceOverlay.MaterialComponents.BottomSheet item>
selector>
< stylename= "ShapeAppearanceOverlay.MaterialComponents.BottomSheet"parent= "">
< itemname= "cornerSizeBottomRight">0dp item>
< itemname= "cornerSizeBottomLeft">0dp item>
style>
注:某些 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 样式中单独指定这些值来减少耦合:
< stylename= ”Widget.MaterialComponents.*”parent= ”...”>
< itemname= ”backgroundTint”>?attr/colorSurface item>
< itemname= "strokeColor">?attr/colorOnSurface item>
< itemname= "strokeWidth">1dp item>
< itemname= ”shapeAppearance”>?attr/shapeAppearanceLargeComponent item>
style>
注:ShapeAppearance样式和背后的 MaterialShapeDrawable类仅支持纯色填充和描边。目前尚不支持渐变,您需要将 XML 可绘制对象与 结合使用来实现渐变。
在应用主题中覆盖形状
接下来我们来看如何通过覆盖相关属性将您选择的形状添加到应用主题中。
首先,您的主题需要妥善处理浅色和深色调色板,同时减少与基础主题的重复。有关这方面的更多信息,请查看 Chris Banes 关于深色主题的,以及他和 Nick Butcher 的 "" 演讲。
设置完成后,在基础主题中覆盖要更改的形状属性:
< stylename= "Theme.App.Base"parent= "Theme.MaterialComponents.*">
...
< itemname= "shapeAppearanceSmallComponent">
@style/ShapeAppearance.App.SmallComponent
item>
< itemname= "shapeAppearanceMediumComponent">
@style/ShapeAppearance.App.MediumComponent
item>
< itemname= "shapeAppearanceLargeComponent">
@style/ShapeAppearance.App.LargeComponent
item>
style>
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
valmsd = MaterialShapeDrawable
// ShapeAppearanceModel constructor
valmsdFromSam = MaterialShapeDrawable(shapeAppearanceModel)
// Style/attr resources constructor (reads shapeAppearance and shapeAppearanceOverlay)
valmsdFromStyles = MaterialShapeDrawable(context, attrs, defStyleAttr, defStyleRes)
// Cast from widget background
valmsdFromWidget = widget.background asMaterialShapeDrawable
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
valsam = ShapeAppearanceModel.builder
.setAllCorners(CornerFamily.CUT, cornerSize)
// Also setTopRightCorner, setAllEdges, etc.
.build
// Style/attr resources builder (reads shapeAppearance and shapeAppearanceOverlay)
valsamFromStyles = ShapeAppearanceModel.builder(context, attrs, defStyleAttr, defStyleRes)
.build
// Build from existing ShapeAppearanceModel
valsamFromExisting = 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)
如需了解更多信息,请参阅之前发布的《》以及 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 之间取值的乘数,方便开发者在动画和转场中使用。
// Setcorner interpolation tohalf ofcurrentcornerSize(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 支持形状主题时要注意些什么。
在 和默认样式中使用 MDC 属性
通过使用 可以让自定义视图支持样式。在保持一致性方面,复用 MDC 中的属性名称是个很好的做法。使用 的默认样式还可以引用 MDC 主题的形状属性为其赋值,同时也可以通过使用 @null/@empty 来实现 MaterialShapeDrawable 背景:
< declare-styleablename= "AppCustomView">
< attrname= "shapeAppearance"/>
...
declare-styleable>
< stylename= "Widget.App.CustomView"parent= "android:Widget">< itemname= "android:background">@null item>< itemname= "shapeAppearance">?attr/shapeAppearanceMediumComponent item>... style>
注意高程和叠加层
如果您希望自定义视图支持高程叠加层或向后移植阴影渲染,最好覆盖 setElevation方法并将其值传递至 MaterialShapeDrawable背景:
classAppCustomView...{...
privatelateinitvarmaterialShapeDrawable: MaterialShapeDrawable
overridefunsetElevation(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+deion+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+deion+of+request
Android 组件示例应用
https://github.com/material-components/material-components-android-examples
如果您已成功实现形状主题,或您在实现期间遇到问题,欢迎在下方评论区和我们分享。
查看 Material Design 设计指南返回搜狐,查看更多
责任编辑: