Jetpack Compose 提供了 Material Design 的实现,后者是一个用于创建数字化界面的综合设计系统。各种 Material Design 组件(按钮、卡片、开关等)是在 Material 主题设置的基础上构建的。Material 主题设置是一种系统化的方法,用于自定义 Material Design 以更好地体现您产品的品牌。一个 Material 主题由颜色、排版和形状属性组成。当您自定义这些属性时,您所做的更改会自动反映在您用来构建应用的组件中。
Jetpack Compose 使用 MaterialTheme
可组合项实现这些概念:
MaterialTheme(
colors = …,
typography = …,
shapes = …
) {
// app content
}
您可以配置传递给 MaterialTheme
的参数来为您的应用设置主题。
图 1. 第一个屏幕截图显示的应用未配置 MaterialTheme
,因此它使用默认的样式设置。第二个屏幕截图显示的应用通过向 MaterialTheme
传递参数,对样式设置进行了自定义。
颜色
颜色在 Compose 中使用 Color
类(一个用来存放数据的简单类)加以模拟。
val Red = Color(0xffff0000)
val Blue = Color(red = 0f, green = 0f, blue = 1f)
虽然您可以按照自己喜欢的方式随意组织这些颜色(采用顶级常量的形式、在单例中加以组织或者以内嵌方式加以定义),但我们强烈建议您在主题中指定颜色并从主题中检索颜色。采用这种方法可轻松支持深色主题和嵌套主题。
图 2. Material 颜色系统。
Compose 提供了 Colors
类来模拟 Material 颜色系统。Colors
提供了构建器函数来创建成套的浅色或深色:
private val Yellow200 = Color(0xffffeb46)
private val Blue200 = Color(0xff91a4fc)
// ...
private val DarkColors = darkColors(
primary = Yellow200,
secondary = Blue200,
// ...
)
private val LightColors = lightColors(
primary = Yellow500,
primaryVariant = Yellow400,
secondary = Blue700,
// ...
)
定义 Colors
后,您可以将其传递给 MaterialTheme
:
MaterialTheme(
colors = if (darkTheme) DarkColors else LightColors
) {
// app content
}
使用主题颜色
您可以使用 MaterialTheme.colors
检索提供给 MaterialTheme
可组合项的 Colors
。
Text(
text = "Hello theming",
color = MaterialTheme.colors.primary
)
表面和内容颜色
许多组件都接受一对颜色和内容颜色:
Surface(
color = MaterialTheme.colors.surface,
contentColor = contentColorFor(color),
// ...
TopAppBar(
backgroundColor = MaterialTheme.colors.primarySurface,
contentColor = contentColorFor(backgroundColor),
// ...
这样一来,您不仅可以设置可组合项的颜色,而且还能为包含在可组合项中的内容提供默认颜色。默认情况下,许多可组合项都使用这种内容颜色。例如,Text
的颜色基于其父项的内容颜色,而 Icon
使用该颜色来设置其色调。
图 3. 设置不同的背景颜色会产生不同的文本和图标颜色。
contentColorFor()
方法可以为任何主题颜色检索适当的“on”颜色。例如,如果您在 Surface
上设置 primary
背景颜色,它会使用此函数将 onPrimary
设置为内容颜色。如果您设置非主题背景颜色,还应指定相应的内容颜色。可以使用 LocalContentColor
在层次结构中的给定位置检索当前背景的首选内容颜色。
内容 Alpha 值
通常,为了突出重点并呈现出视觉上的层次感,对内容的突出程度需要视情况而异。Material Design 文本易读性建议一文建议通过不同的不透明度来表示不同的重要程度。
Jetpack Compose 通过 LocalContentAlpha
来实现这一点。您可以通过为此 CompositionLocal
提供一个值来为层次结构指定内容 Alpha 值。嵌套的可组合项可以使用该值来对其内容进行 Alpha 处理。例如,Text
和 Icon
默认使用 LocalContentColor
的组合,已调整为使用 LocalContentAlpha
。Material 指定了一些标准 Alpha 值(high
、medium
、disabled
),这些值由 ContentAlpha
对象加以模拟。
// By default, both Icon & Text use the combination of LocalContentColor &
// LocalContentAlpha. De-emphasize content by setting content alpha
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text(/*...*/)
}
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
Icon(/*...*/)
Text(/*...*/)
}
如需详细了解 CompositionLocal
,请参阅“使用 CompositionLocal 将数据的作用域限定在局部”指南。
图 4. 对文本采用不同的突出程度,以便从视觉上传达信息的层次感。第一行文本是标题,其中包含最重要的信息,因此使用 ContentAlpha.high
。第二行文本包含不太重要的元数据,因此使用 ContentAlpha.medium
。
深色主题
在 Compose 中,您可以通过向 MaterialTheme
可组合项提供多组不同的 Colors
来实现浅色主题和深色主题:
@Composable
fun MyTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
MaterialTheme(
colors = if (darkTheme) DarkColors else LightColors,
/*...*/
content = content
)
}
在本例中,MaterialTheme
封装在其自己的可组合函数中,该函数接受一个用于指定是否使用深色主题的参数。在这种情况下,该函数会通过查询设备主题设置来获取 darkTheme
的默认值。
您可以使用如下所示的代码来检查当前的 Colors
是浅色还是深色:
val isLightTheme = MaterialTheme.colors.isLight
Icon(
painterResource(
id = if (isLightTheme) {
R.drawable.ic_sun_24dp
} else {
R.drawable.ic_moon_24dp
}
),
contentDescription = "Theme"
)
高度叠加层
在 Material 中,采用深色主题且高度较高的 Surface 会获得高度叠加层,这会使其背景颜色变浅。表面的高度(升到较接近隐式光源)越高,表面的颜色就越浅。
使用深色时,Surface
可组合项会自动应用这些叠加层;而对于使用表面的任何其他 Material 可组合项:
Surface(
elevation = 2.dp,
color = MaterialTheme.colors.surface, // color will be adjusted for elevation
/*...*/
) { /*...*/ }
图 5. 卡片和底部导航栏均将 surface
颜色作为背景颜色。由于卡片和底部导航栏相对于背景处于不同的高度,因此它们的颜色稍有差异:卡片颜色比背景颜色浅,底部导航栏的颜色则比卡片颜色浅。
对于未使用 Surface
的自定义场景,请使用 LocalElevationOverlay
,它是一个包含由 Surface
组件所用的 ElevationOverlay
的 CompositionLocal
:
// Elevation overlays
// Implemented in Surface (and any components that use it)
val color = MaterialTheme.colors.surface
val elevation = 4.dp
val overlaidColor = LocalElevationOverlay.current?.apply(
color, elevation
)
如需停用高度叠加层,请在可组合项层次结构中的所需位置提供 null
:
MyTheme {
CompositionLocalProvider(LocalElevationOverlay provides null) {
// Content without elevation overlays
}
}
有限的色彩强度
Material 建议您对深色主题应用有限的色彩强度,方法是在大多数情况下优先使用 surface
颜色(而非 primary
颜色)。TopAppBar
和 BottomNavigation
等 Material 可组合项默认会实现此行为。
图 6. 色彩强度有限的 Material 深色主题。顶部应用栏使用浅色主题的主要颜色,并使用深色主题的 Surface 颜色。
对于自定义场景,请使用 primarySurface
扩展属性:
Surface(
// Switches between primary in light theme and surface in dark theme
color = MaterialTheme.colors.primarySurface,
/*...*/
) { /*...*/ }
排版
Material 定义了一个字型系统,鼓励您使用少量从语义上命名的样式。
图 7. Material 类型系统。
Compose 使用 Typography
、TextStyle
和字体相关类来实现字型系统。Typography
构造函数可以提供每种样式的默认值,因此您可以省略不希望自定义的任何样式:
val Rubik = FontFamily(
Font(R.font.rubik_regular),
Font(R.font.rubik_medium, FontWeight.W500),
Font(R.font.rubik_bold, FontWeight.Bold)
)
val MyTypography = Typography(
h1 = TextStyle(
fontFamily = Rubik,
fontWeight = FontWeight.W300,
fontSize = 96.sp
),
body1 = TextStyle(
fontFamily = Rubik,
fontWeight = FontWeight.W600,
fontSize = 16.sp
)
/*...*/
)
MaterialTheme(typography = MyTypography, /*...*/)
如果您希望自始至终使用同一字型,请指定 defaultFontFamily parameter
,并省略所有 TextStyle
元素的 fontFamily
:
val typography = Typography(defaultFontFamily = Rubik)
MaterialTheme(typography = typography, /*...*/)
使用文本样式
通过 MaterialTheme.typography
访问 TextStyle
。使用如下所示的代码检索 TextStyle
:
Text(
text = "Subtitle2 styled",
style = MaterialTheme.typography.subtitle2
)
图 8. 使用一系列字型和样式来表达您的品牌。
形状
Material 定义了一个形状系统,可让您定义大型、中型和小型组件的形状。
图 9. Material 形状系统。
Compose 使用 Shapes
类来实现形状系统,该类可让您为每个大小类别指定一个 CornerBasedShape
:
val Shapes = Shapes(
small = RoundedCornerShape(percent = 50),
medium = RoundedCornerShape(0f),
large = CutCornerShape(
topStart = 16.dp,
topEnd = 0.dp,
bottomEnd = 0.dp,
bottomStart = 16.dp
)
)
MaterialTheme(shapes = Shapes, /*...*/)
默认情况下,许多组件使用这些形状。例如,Button
、TextField
和 FloatingActionButton
默认为“小”,AlertDialog
默认为“中”,而 ModalDrawer
默认为“大”。如需查看完整的对应关系,请参阅形状主题参考文档。
使用形状
通过 MaterialTheme.shapes
访问 Shape
。使用如下所示的代码检索 Shape
:
Surface(
shape = MaterialTheme.shapes.medium, /*...*/
) {
/*...*/
}
图 10. 使用形状来表达品牌或状态。
默认样式
Compose 中没有与 Android Views 的默认样式等效的概念。您可以通过自行创建用于封装 Material 组件的“重载”可组合函数来提供类似功能。例如,如需创建按钮的样式,请将一个按钮封装在您自己的可组合函数中,直接设置您希望更改的参数,并将其他参数以参数形式提供给包含该按钮的可组合项。
@Composable
fun MyButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
content: @Composable RowScope.() -> Unit
) {
Button(
colors = ButtonDefaults.buttonColors(
backgroundColor = MaterialTheme.colors.secondary
),
onClick = onClick,
modifier = modifier,
content = content
)
}
主题叠加层
您可以通过嵌套 MaterialTheme
可组合项在 Compose 中实现与 Android View 等效的主题叠加层。由于 MaterialTheme
默认为颜色、排版和形状使用当前主题值,因此如果某个主题仅设置了其中一个参数,其他参数将保留默认值。
此外,将基于 View 的界面迁移到 Compose 时,请注意 android:theme
属性的用法。您可能需要在 Compose 界面树的相应部分添加新的 MaterialTheme
。
在 Owl 示例中,详细信息屏幕上的大部分区域使用 PinkTheme
,相关部分则使用 BlueTheme
,如下方的屏幕截图和代码所示。
图 11. Owl 示例中的嵌套主题。
@Composable
fun DetailsScreen(/* ... */) {
PinkTheme {
// other content
RelatedSection()
}
}
@Composable
fun RelatedSection(/* ... */) {
BlueTheme {
// content
}
}
组件状态
支持交互(点击、切换等)的 Material 组件可以处于不同的视觉状态,其中包括“已启用”“已停用”“已按下”等。
可组合项通常具有 enabled
参数。将其设置为 false
可防止交互,更改颜色和高度等属性可直观地传达组件状态。
图 12. enabled = true
的按钮(左侧)及 enabled = false
的按钮(右侧)。
在大多数情况下,您可为颜色和高度等使用默认值。如果您想配置在不同状态下使用的值,可以使用类和便捷函数。按钮示例如下所示:
Button(
onClick = { /* ... */ },
enabled = true,
// Custom colors for different states
colors = ButtonDefaults.buttonColors(
backgroundColor = MaterialTheme.colors.secondary,
disabledBackgroundColor = MaterialTheme.colors.onBackground
.copy(alpha = 0.2f)
.compositeOver(MaterialTheme.colors.background)
// Also contentColor and disabledContentColor
),
// Custom elevation for different states
elevation = ButtonDefaults.elevation(
defaultElevation = 8.dp,
disabledElevation = 2.dp,
// Also pressedElevation
)
) { /* ... */ }
图 13. 采用经过调整的颜色值和高度值的 enabled = true
的按钮(左侧)及 enabled = false
的按钮(右侧)。
涟漪
Material 组件通过涟漪来表示正在进行交互。如果您在层次结构中使用 MaterialTheme
,Ripple
将被用作 clickable
和 indication
等修饰符内的默认 Indication
。
在大多数情况下,您可以使用默认的 Ripple
。如果您想配置其外观,可以使用 RippleTheme
来更改颜色和 Alpha 内容等属性。
您可以扩展 RippleTheme
并使用 defaultRippleColor
和 defaultRippleAlpha
实用函数。然后,您可以使用 LocalRippleTheme
在层次结构中提供自定义涟漪主题:
@Composable
fun MyApp() {
MaterialTheme {
CompositionLocalProvider(
LocalRippleTheme provides SecondaryRippleTheme
) {
// App content
}
}
}
@Immutable
private object SecondaryRippleTheme : RippleTheme {
@Composable
override fun defaultColor() = RippleTheme.defaultRippleColor(
contentColor = MaterialTheme.colors.secondary,
lightTheme = MaterialTheme.colors.isLight
)
@Composable
override fun rippleAlpha() = RippleTheme.defaultRippleAlpha(
contentColor = MaterialTheme.colors.secondary,
lightTheme = MaterialTheme.colors.isLight
)
}
图 14. 通过 RippleTheme
所提供的涟漪值不同的按钮。
了解详情
如需详细了解 Compose 中的 Material 主题设置,请参阅下面列出的其他资源。