Compose 中的 Material 主题设置

Jetpack Compose 提供了 Material Design 的实现,后者是一个用于创建数字化界面的综合设计系统。Material Design 组件(按钮、卡片、开关等)在 Material 主题设置的基础上构建而成,Material 主题设置是一种系统化的方法,用于自定义 Material Design 以更好地反映您产品的品牌。一个 Material 主题由颜色排版形状属性组成。当您自定义这些属性时,您所做的更改会自动反映在您用来构建应用的组件中。

Jetpack Compose 使用 MaterialTheme 可组合项实现这些概念:

MaterialTheme(
    colors = …,
    typography = …,
    shapes = …
) {
    // app content
}

您可以配置传递给 MaterialTheme 的参数来为您的应用设置主题。

两个对比鲜明的屏幕截图。第一个屏幕截图使用默认的 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: Color = MaterialTheme.colors.surface,
    contentColor: Color = contentColorFor(color),
    // ...

TopAppBar(
    backgroundColor: Color = MaterialTheme.colors.primarySurface,
    contentColor: Color = contentColorFor(backgroundColor),
    // ...

这样一来,您不仅可以设置可组合项的颜色,而且还能为包含在可组合项中的内容提供默认颜色。默认情况下,许多可组合项都使用这种内容颜色。例如,Text 的颜色基于其父项的内容颜色,而 Icon 使用该颜色来设置其色调。

具有不同颜色的同一横幅的两个示例

图 3. 设置不同的背景颜色会生成不同的文本和图标颜色。

contentColorFor() 方法可以为任何主题颜色检索适当的“on”颜色。例如,如果您在 Surface 上设置 primary 背景颜色,它会使用此函数将 onPrimary 设置为内容颜色。如果您设置非主题背景颜色,还应指定相应的内容颜色。请使用 LocalContentColor 来检索层次结构中给定位置当前背景的首选内容颜色。

内容 Alpha 值

通常情况下,您需要根据不同情况来确定内容的强调程度,以突出重点并呈现出视觉上的层次感。Material Design 文本易读性建议一文建议通过不同的不透明度来表示不同的重要程度。

Jetpack Compose 通过 LocalContentAlpha 实现此功能。您可以通过为此 CompositionLocal 提供一个值来为层次结构指定内容 Alpha 值。嵌套可组合项可以使用该值对其内容应用 Alpha 处理。例如,TextIcon 默认使用 LocalContentColor 的组合,已调整为使用 LocalContentAlpha。Material 指定了一些标准 Alpha 值(highmediumdisabled),这些值由 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 中,采用深色主题且高度较高的表面会获得高度叠加层,这会使其背景颜色变浅。表面的高度(升到较接近隐式光源)越高,表面的颜色就越浅。

使用深色时,对于使用表面的任何其他 Material 可组合向,这些叠加层由 Surface 可组合项自动应用:

Surface(
    elevation = 2.dp,
    color = MaterialTheme.colors.surface, // color will be adjusted for elevation
    /*...*/
) { /*...*/ }

一个应用的屏幕截图,显示对位于不同高度的元素使用了略有不同的颜色

图 5. 卡片和底部导航栏均将 surface 颜色作为背景颜色。由于卡片和底部导航栏在后台上方处于不同高度,因此它们的颜色稍有差异:卡片颜色比背景颜色浅,底部导航栏的颜色则比卡片颜色浅。

对于不未使用 Surface 的自定义场景,请使用 LocalElevationOverlay,它是一个包含由 Surface 组件所用的 ElevationOverlayCompositionLocal

// 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 颜色)。TopAppBarBottomNavigation 等 Material 可组合项默认会实现此行为。

图 6. 色彩强度有限的 Material 深色主题。顶部应用栏使用浅色主题的主要颜色,并使用深色主题的表面颜色。

对于自定义场景,请使用 primarySurface 扩展属性:

Surface(
    // Switches between primary in light theme and surface in dark theme
    color = MaterialTheme.colors.primarySurface,
    /*...*/
) { /*...*/ }

排版

Material 定义了一个字型系统,鼓励您使用少量从语义上命名的样式。

不同样式的几种不同字型的示例

图 7. Material 类型系统。

Compose 使用 TypographyTextStyle字体相关类来实现字型系统。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 定义了一个形状系统,可让您定义大型、中型和小型组件的形状。

显示各种 Material Design 形状

图 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, /*...*/)

默认情况下,许多组件使用这些形状。例如,ButtonTextFieldFloatingActionButton 默认为“小”,AlertDialog 默认为“中”,而 ModalDrawer 默认为“大”。如需查看完整的对应关系,请参阅形状主题参考文档

使用形状

通过 MaterialTheme.shapes 访问 Shape。使用如下所示的代码检索 Shape

Surface(
    shape = MaterialTheme.shapes.medium, /*...*/
) {
    /*...*/
}

一个应用的屏幕截图,该应用使用 Material 形状来表达元素所处的状态

图 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 组件通过涟漪来表示正在进行交互。如果您在层次结构中使用 MaterialThemeRipple 将被用作 clickableindication 等修饰符内的默认 Indication

在大多数情况下,您可以使用默认的 Ripple。如果您想配置其外观,可以使用 RippleTheme 来更改颜色和 Alpha 内容等属性。

您可以扩展 RippleTheme 并使用 defaultRippleColordefaultRippleAlpha 实用函数。然后,您可以使用 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
  )
}

alt_text

图 14. 通过 RippleTheme 所提供的涟漪值不同的按钮。

Material Design 3 和 Material You

Jetpack Compose 提供了 Material Design 3 的实现,后者是 Material Design 的下一代产品。Material 3 包括更新后的主题和组件,以及动态配色等 Material You 个性化功能,旨在与新的 Android 12 视觉风格和系统界面相得益彰。

首先,将新的 Compose Material 3 依赖项添加到 build.gradle 文件中:

implementation "androidx.compose.material3:material3:material3_version"

M3 主题包含配色方案排版值,并且很快还会对形状进行更新。当您自定义这些值时,您所做的更改会自动反映在您用来构建应用的 M3 组件中。

Jetpack Compose 使用新的 M3 MaterialTheme 可组合项实现这些概念:

MaterialTheme(
    colorScheme = …,
    typography = …
    // Updates to shapes coming soon
) {
    // M3 app content
}

您可以配置传递给 M3 MaterialTheme 的参数来为您的应用设置主题。

图 15. 前两个屏幕截图显示的应用未配置 M3 MaterialTheme,因此它使用默认的样式设置。后两个屏幕截图显示的应用将参数传递给 MaterialTheme,对样式设置进行了自定义。

配色方案

Material Design 3 将颜色分成指定颜色“槽”,例如 Material 3 组件使用的“primary”、“background”和“error”。这些槽一起形成一种配色方案。每个槽所用的颜色值取自一组色调调色板,这些调色板旨在满足无障碍功能要求(例如,“primary”槽可以保证与“on primary”槽形成对比)。配色方案为浅色主题和深色主题提供了新的默认基准颜色。

图 16. Material 3 色调调色板和配色方案,包含基准颜色值。

Compose 提供了 ColorScheme 类来对 Material 3 配色方案进行建模。ColorScheme 提供了构建器函数来创建浅色深色配色方案:

private val Blue40 = Color(0xff1e40ff​​)
private val DarkBlue40 = Color(0xff3e41f4)
private val Yellow40 = Color(0xff7d5700)
// Remaining colors from tonal palettes

private val LightColorScheme = lightColorScheme(
    primary = Blue40,
    secondary = DarkBlue40,
    tertiary = Yellow40,
    // error, primaryContainer, onSecondary, etc.
)
private val DarkColorScheme = darkColorScheme(
    primary = Blue80,
    secondary = DarkBlue80,
    tertiary = Yellow80,
    // error, primaryContainer, onSecondary, etc.
)

定义 ColorScheme 后,您可以将其传递给 M3 MaterialTheme

val darkTheme = isSystemInDarkTheme()
MaterialTheme(
    colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme
) {
    // M3 app content
}

生成配色方案

虽然您可以手动创建自定义 ColorScheme,但使用品牌中的源颜色通常更容易生成配色方案。您可以使用 Material 主题构建器工具执行此操作,并且可以选择导出 Compose 主题代码。

图 17. 由 Material 主题构建器工具生成的 Material 3 色调调色板和配色方案,包含自定义颜色值。

动态配色方案

动态颜色是 Material You 的关键部分,使用此功能时,算法会从用户的壁纸中派生自定义颜色,以将其应用到其应用和系统界面。您可以将此调色板作为起始点,来生成完整的浅色和深色配色方案。

动态颜色适用于 Android 12 及更高版本。如果动态颜色可用,您可以设置动态 ColorScheme。如果不可用,您可以回退到使用自定义浅色或深色 ColorScheme

ColorScheme 提供了构建器函数来创建动态浅色深色配色方案:

// Dynamic color is available on Android 12+
val dynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
val colorScheme = when {
    dynamicColor && darkTheme -> dynamicDarkColorScheme(LocalContext.current)
    dynamicColor && !darkTheme -> dynamicLightColorScheme(LocalContext.current)
    darkTheme -> DarkColorScheme
    else -> LightColorScheme
}

图 18. 前两个屏幕截图显示的应用使用基于红色壁纸的动态 ColorScheme。后两个屏幕截图显示的应用使用基于蓝色壁纸的动态 ColorScheme

使用配色方案颜色

您可以使用 MaterialTheme.colorScheme 检索提供给 M3 MaterialTheme 可组合项的 ColorScheme

Text(
    text = "Hello M3 theming",
    color = MaterialTheme.colorScheme.tertiary
)

排版

Material Design 3 定义了一个字体比例,包括改写自 Material Design 2 的文本样式。命名和分组已简化为:显示、大标题、标题、正文和标签,每个都有大号、中号和小号。

图 19. Material 3 字体比例与 Material 2 字体比例。

Compose 提供了新的 M3 Typography 类,以及现有的 TextStyle字体相关类,用以对 Material 3 字体比例进行建模:

val KarlaFontFamily = FontFamily(
    Font(R.font.karla_regular),
    Font(R.font.karla_bold, FontWeight.Bold)
)

val AppTypography = Typography(
    bodyLarge = TextStyle(
        fontFamily = KarlaFontFamily,
        fontWeight = FontWeight.Normal,
        fontSize = 16.sp,
        lineHeight = 24.sp,
        letterSpacing = 0.15.sp
    ),
    // titleMedium, labelSmall, etc.
)

定义 Typography 后,您可以将其传递给 M3 MaterialTheme

MaterialTheme(
    typography = AppTypography
) {
    // M3 app content
}

使用文本样式

您可以使用 MaterialTheme.typography 检索提供给 M3 MaterialTheme 可组合项的 Typography

Text(
    text = "Hello M3 theming",
    style = MaterialTheme.typography.bodyLarge
)

高度

Material 3 主要使用色调颜色叠加叠加层来表示高度。这是一种新的方式,用于区分容器和表面,增加的色调高度除了使用阴影外,还使用更突出的色调。

深色主题中的高度叠加层在 Material 3 中也已更改为色调颜色叠加层。

叠加层颜色来自主要颜色槽。

图 20. Material 3 的高度与 Material 2 的高度(在浅色和深色主题下)。

M3 Surface 是大多数 M3 组件的后备可组合项,同时支持色调和阴影高度:

Surface(
    tonalElevation = 16.dp,
    shadowElevation = 16.dp
) {
    // Surface content
}

系统界面

Material 的某些部分来自新的 Android 12 视觉样式和系统界面。发生变化的两个主要方面是涟漪效果和滚动效果。您无需执行任何额外操作即可实施这些更改。

涟漪

现在,按下时,涟漪会使用微小的火花照射表面。Compose Material 涟漪使用 Android 上的底层平台 RippleDrawable,因此,Android 12 及更高版本上提供的火花涟漪适用于所有 Material 组件。

图 21. Android 12 中的涟漪与 Android 12 之前版本中的涟漪。

滚动

滚动现在会在滚动容器的边缘使用拉伸效果。在滚动容器可组合项(例如 Compose Foundation 1.1.0 及更高版本中的 LazyColumnLazyRowLazyVerticalGrid)中,拉伸滚动默认处于开启状态,无论 API 级别为何。

图 22. 拉伸滚动。

了解详情

如需详细了解 Compose 中的 Material 主题设置,请参阅下面列出的其他资源。

Codelab

视频