Jetpack Compose 提供了 Material Design 的实现,这是一个用于创建数字界面的综合设计系统。 Material Design 组件(按钮、卡片、开关等)构建在 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
基于其父级的 content color 设置其颜色,而 Icon
使用该颜色来设置其色调。
图 3. 设置不同的背景颜色会产生不同的文本和图标颜色。
contentColorFor()
方法检索任何主题颜色的适当“on”颜色。例如,如果在 Surface
上设置了 primary
背景颜色,它会使用此函数将 onPrimary
作为内容颜色。如果设置了非主题背景颜色,则还应指定适当的内容颜色。使用 LocalContentColor
检索当前背景在层次结构中给定位置的首选内容颜色。
内容透明度
通常,您希望改变强调内容的程度,以传达重要性并提供视觉层次结构。Material Design 文本易读性建议 建议使用不同级别的不透明度来传达不同的重要性级别。
Jetpack Compose 通过 LocalContentAlpha
实现此功能。您可以通过 为该 CompositionLocal
提供一个值 来为层次结构指定内容透明度。嵌套的可组合项可以使用此值将其内容应用透明度处理。例如,Text
和 Icon
默认使用 LocalContentColor
与 LocalContentAlpha
的组合。Material 指定了一些标准的透明度值(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_24 } else { R.drawable.ic_moon_24 } ), contentDescription = "Theme" )
海拔叠加
在 Material 中,深色主题中具有较高海拔的表面会收到 海拔叠加,这会使其背景变亮。表面的海拔越高(将其抬升到更靠近隐含光源的位置),该表面就会变得越亮。
当使用深色时,这些叠加层会由 Surface
可组合项自动应用,以及任何其他使用表面的 Material 可组合项。
Surface( elevation = 2.dp, color = MaterialTheme.colors.surface, // color will be adjusted for elevation /*...*/ ) { /*...*/ }
图 5. 卡片和底部导航都使用 surface
颜色作为其背景。由于卡片和底部导航在背景上方具有不同的海拔高度,因此它们的颜色略有不同——卡片比背景亮,底部导航比卡片亮。
对于不涉及 Surface
的自定义场景,请使用 LocalElevationOverlay
,这是一个包含 ElevationOverlay
的 CompositionLocal
,该 ElevationOverlay
由 Surface
组件使用。
// 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 深色主题。顶部应用栏在浅色主题中使用 primary 颜色,在深色主题中使用 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 raleway = FontFamily( Font(R.font.raleway_regular), Font(R.font.raleway_medium, FontWeight.W500), Font(R.font.raleway_semibold, FontWeight.SemiBold) ) val myTypography = Typography( h1 = TextStyle( fontFamily = raleway, fontWeight = FontWeight.W300, fontSize = 96.sp ), body1 = TextStyle( fontFamily = raleway, fontWeight = FontWeight.W600, fontSize = 16.sp ) /*...*/ ) MaterialTheme(typography = myTypography, /*...*/) { /*...*/ }
如果希望在整个应用中使用相同的字体,请指定 defaultFontFamily 参数
并省略任何 TextStyle
元素的 fontFamily
。
val typography = Typography(defaultFontFamily = raleway) MaterialTheme(typography = typography, /*...*/) { /*...*/ }
使用文本样式
TextStyle
可以通过MaterialTheme.typography
访问。像这样检索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
默认使用大型——有关完整映射,请参阅形状方案参考。
使用形状
Shape
可以通过MaterialTheme.shapes
访问。使用如下代码检索Shape
Surface( shape = MaterialTheme.shapes.medium, /*...*/ ) { /*...*/ }
图 10. 使用形状来表达品牌或状态。
默认样式
Compose 中没有与 Android 视图中的默认样式等效的概念。您可以通过创建自己的“重载”可组合函数来包装 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
可组合函数来实现 Android 视图中的主题叠加 的等效功能。因为MaterialTheme
将颜色、排版和形状默认为当前主题值,所以如果主题仅设置其中一个参数,则其他参数将保留其默认值。
此外,在将基于视图的屏幕迁移到 Compose 时,请注意 android:theme
属性的使用情况。您可能需要在 Compose UI 树的该部分中使用新的MaterialTheme
。
在此示例中,详细信息屏幕对大部分屏幕使用PinkTheme
,然后对相关部分使用BlueTheme
。请参见下面的屏幕截图和代码。
图 11. 嵌套主题。
@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 主题的更多信息,请参阅以下其他资源。
Codelabs
视频
推荐内容
- 注意:关闭 JavaScript 时将显示链接文本
- Compose 中的自定义设计系统
- 将 Compose 中的 Material 2 迁移到 Material 3
- Compose 中的可访问性