Compose 中的主题

借助 Jetpack Compose,您可以通过应用主题,轻松地赋予应用一致的外观和风格。您可以自定义 Compose 的 Material Design 实现,使其适合您产品的品牌。如果这不符合您的需求,您可以使用 Compose 的公共 API 构建自定义设计系统。

应用范围的主题

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

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)

虽然您可以按照自己喜欢的方式随意组织这些颜色(作为顶级常量、在单例中或以内嵌方式定义),但我们强烈建议您在主题中指定颜色并从中检索颜色。采用这种方法可以支持多个主题,如深色主题

主题调色板的示例

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 使用该颜色来设置其色调。

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

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

contentColorFor() 方法可以为任何主题颜色检索适当的“on”颜色。例如,如果您设置 primary 背景,就会将 onPrimary 设为内容颜色。如果您设置非主题背景颜色,还应指定合理的内容颜色。使用 LocalContentColor 可检索与当前背景形成对比的当前内容颜色。

内容 Alpha 值

通常,我们希望根据不同情况来确定内容的强调程度,以突出重点并体现出视觉上的层次感。Material Design 建议采用不同的不透明度来传达这些不同的重要程度。

Jetpack Compose 通过 LocalContentAlpha 实现此功能。您可以通过为此 CompositionLocal 提供一个值来为层次结构指定内容 Alpha 值。子可组合项可以使用此值,例如 TextIcon 默认使用 LocalContentColor 的组合,已调整为使用 LocalContentAlpha。Material 指定了一些标准 Alpha 值(highmediumdisabled),这些值由 ContentAlpha 对象建模。请注意,MaterialTheme 默认将 LocalContentAlpha 设置为 ContentAlpha.high

// 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(/*...*/)
}

文章标题的屏幕截图,显示了不同级别的文本强调效果

图 3. 对文本应用不同级别的强调效果,以直观地传达信息层次结构。

深色主题

在 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

此值由 lightColors()darkColors() 构建器函数设置。

在 Material 中,采用深色主题且高度较高的表面会获得高度叠加层,这会使其背景颜色变浅。使用深色时,这些叠加层由 Surface 可组合项自动实现:

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

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

图 4. 卡片和底部导航栏像背景一样进行 surface 着色,但由于它们位于较高的高度,因此颜色略浅。

扩展 Material 颜色

Compose 对 Material 的颜色主题进行相近建模,使得遵循 Material 准则既简单又能确保类型安全。如果您需要扩展颜色集,可以实现自己的颜色系统(如下所示),也可以添加扩展程序:

val Colors.snackbarAction: Color
    @Composable get() = if (isLight) Red300 else Red700

字体排版

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 参数,并省略所有 TextStyle 元素的 fontFamily

val typography = Typography(defaultFontFamily = Rubik)
MaterialTheme(typography = typography, /*...*/)

使用文本样式

您可以从主题检索 TextStyle,如以下示例所示:

Text(
    text = "Subtitle2 styled",
    style = MaterialTheme.typography.subtitle2
)

显示不同用途的不同字型混合在一起的屏幕截图

图 5. 使用一系列字型和样式来表达您的品牌。

形状

Material 定义了一个形状系统,可让您定义大型、中型和小型组件的形状。

显示各种 Material Design 形状

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 默认为“大”。如需查看完整的对应关系,请参阅形状方案参考文档。

使用形状

您可以从主题检索形状:

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

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

图 6. 使用形状来表达品牌或状态。

组件样式

在 Compose 中,没有关于组件样式的明确概念,因为您可以通过创建自己的可组合项来提供此功能。例如,如需创建按钮的样式,请将一个按钮封装在您自己的可组合函数中,直接设置您希望更改的参数,并将其他参数以参数形式提供给包含该按钮的可组合项。

@Composable
fun LoginButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Button(
        colors = ButtonDefaults.buttonColors(
            backgroundColor = MaterialTheme.colors.secondary
        ),
        onClick = onClick,
        modifier = modifier,
        content = content
    )
}

自定义设计系统

虽然 Material 是我们推荐的设计系统并且 Jetpack Compose 附带了 Material 的实现,但您并非只能使用它。您完全可以采用同样的方式创建自己的设计系统;Material 完全基于公共 API,您可以使用这些 API 来实现此目的。

有关如何构建自定义设计系统的完整说明不在本文档讨论的范围内,但您可以参阅以下资源:

了解详情

如需了解详情,请参阅 Jetpack Compose 主题 Codelab