Compose 中的 Material Design 3

Jetpack Compose 提供了 Material Design 3 的实现,它是 Material Design 的下一个演进版本。Material 3 包含更新的主题、组件和 Material You 个性化功能,例如动态颜色,旨在与 Android 12 及更高版本上的新视觉风格和系统 UI 保持一致。

下面,我们以 Reply 示例应用程序 为例,演示 Material Design 3 的实现。Reply 示例完全基于 Material Design 3。

Reply sample app using Material Design 3
图 1. 使用 Material Design 3 的 Reply 示例应用程序

依赖项

要在您的 Compose 应用程序中开始使用 Material 3,请将 Compose Material 3 依赖项添加到您的 build.gradle 文件中

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

添加依赖项后,您可以开始将 Material Design 系统(包括颜色、排版和形状)添加到您的应用程序中。

实验性 API

一些 M3 API 被认为是实验性的。在这种情况下,您需要使用 ExperimentalMaterial3Api 注释在函数或文件级别选择加入

// import androidx.compose.material3.ExperimentalMaterial3Api
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppComposable() {
    // M3 composables
}

Material 主题

M3 主题包含以下子系统:配色方案排版形状。当您自定义这些值时,您的更改会自动反映在您用来构建应用程序的 M3 组件中。

Subsystems of Material design: Color, Typography and Shapes
图 2. Material Design 的子系统:颜色、排版和形状

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

MaterialTheme(
    colorScheme = /* ...
    typography = /* ...
    shapes = /* ...
) {
    // M3 app content
}

要对应用程序内容进行主题化,请定义特定于您的应用程序的配色方案、排版和形状。

配色方案

配色方案的基础是一组五个关键颜色。每种颜色都与一个包含 13 个色调的色调调色板相关联,这些色调由 Material 3 组件使用。例如,这是 Reply 浅色主题的配色方案

Reply sample app light color scheme
图 3. Reply 示例应用程序的浅色配色方案

详细了解 配色方案和颜色角色

生成配色方案

虽然您可以手动创建自定义的 ColorScheme,但通常使用品牌中的源颜色来生成一个更方便。Material Theme Builder 工具允许您执行此操作,并可以选择导出 Compose 主题代码。将生成以下文件

  • Color.kt 包含您主题的颜色,其中包含为浅色和深色主题颜色定义的所有角色。

val md_theme_light_primary = Color(0xFF476810)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFFC7F089)
// ..
// ..

val md_theme_dark_primary = Color(0xFFACD370)
val md_theme_dark_onPrimary = Color(0xFF213600)
val md_theme_dark_primaryContainer = Color(0xFF324F00)
// ..
// ..

  • Theme.kt 包含浅色和深色配色方案以及应用程序主题的设置。

private val LightColorScheme = lightColorScheme(
    primary = md_theme_light_primary,
    onPrimary = md_theme_light_onPrimary,
    primaryContainer = md_theme_light_primaryContainer,
    // ..
)
private val DarkColorScheme = darkColorScheme(
    primary = md_theme_dark_primary,
    onPrimary = md_theme_dark_onPrimary,
    primaryContainer = md_theme_dark_primaryContainer,
    // ..
)

@Composable
fun ReplyTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colorScheme =
        if (!darkTheme) {
            LightColorScheme
        } else {
            DarkColorScheme
        }
    MaterialTheme(
        colorScheme = colorScheme,
        content = content
    )
}

要支持浅色和深色主题,请使用 isSystemInDarkTheme()。根据系统设置,定义要使用的配色方案:浅色或深色。

动态配色方案

动态颜色 是 Material You 的关键部分,其中一个算法从用户的壁纸中推导出自定义颜色,并将其应用于其应用程序和系统 UI。此调色板用作生成浅色和深色配色方案的起点。

Reply sample app dynamic theming from wallpaper (left) and default app theming (right)
图 4. Reply 示例应用程序从壁纸中进行动态主题化(左)和默认应用程序主题化(右)

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

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

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

颜色用法

您可以通过 MaterialTheme.colorScheme 访问应用程序中的 Material 主题颜色

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

每个颜色角色可以在各种位置使用,具体取决于组件的状态、突出程度和强调程度。

  • Primary 是基础颜色,用于主要组件,例如突出的按钮、活动状态和高架表面的色调。
  • 次要关键颜色用于 UI 中不太突出的组件,例如筛选芯片,并扩展了颜色表达的机会。
  • 第三关键颜色用于推导出对比色调的角色,这些色调可以用来平衡主要颜色和次要颜色,或将重点吸引到某个元素。

Reply 示例应用程序设计在 primary-container 之上使用 on-primary-container 颜色,以突出显示选定项目。

Primary container and text fields with on-primary-container color.
图 5. 带有 on-primary-container 颜色的 Primary container 和文本字段。

Card(
    colors = CardDefaults.cardColors(
        containerColor =
        if (isSelected) MaterialTheme.colorScheme.primaryContainer
        else
            MaterialTheme.colorScheme.surfaceVariant
    )
) {
    Text(
        text = "Dinner club",
        style = MaterialTheme.typography.bodyLarge,
        color =
        if (isSelected) MaterialTheme.colorScheme.onPrimaryContainer
        else MaterialTheme.colorScheme.onSurface,
    )
}

在这里,您可以在 Reply 导航抽屉中看到如何使用 secondary 和 tertiary container 颜色与 primary container 颜色形成对比,以创建强调和突出效果。

Tertiary-container and on-tertiary-container combination for Floating Action button.
图 6. 用于浮动操作按钮的 tertiary-container 和 on-tertiary-container 组合。

排版

Material Design 3 定义了一个 类型比例,包括从 Material Design 2 中改编的文本样式。命名和分组已简化为:显示、标题、标题、正文和标签,每个都具有大、中和小尺寸。

Default typography scale for Material design 3
图 7. Material Design 3 的默认排版比例
M3 默认字体大小/行高
displayLarge Roboto 57/64
displayMedium Roboto 45/52
displaySmall Roboto 36/44
headlineLarge Roboto 32/40
headlineMedium Roboto 28/36
headlineSmall Roboto 24/32
titleLarge 新 - Roboto Medium 22/28
titleMedium Roboto Medium 16/24
titleSmall Roboto Medium 14/20
bodyLarge Roboto 16/24
bodyMedium Roboto 14/20
bodySmall Roboto 12/16
labelLarge Roboto Medium 14/20
labelMedium Roboto Medium 12/16
labelSmall 新 Roboto Medium,11/16

定义排版

Compose 提供了 M3 的 Typography 类 - 以及现有的 TextStyle字体相关 类 - 用于模拟 Material 3 类型比例。Typography 构造函数为每种样式提供默认值,因此您可以省略您不想自定义的任何参数

val replyTypography = Typography(
    titleLarge = TextStyle(
        fontWeight = FontWeight.SemiBold,
        fontSize = 22.sp,
        lineHeight = 28.sp,
        letterSpacing = 0.sp
    ),
    titleMedium = TextStyle(
        fontWeight = FontWeight.SemiBold,
        fontSize = 16.sp,
        lineHeight = 24.sp,
        letterSpacing = 0.15.sp
    ),
    // ..
)
// ..

Body large, body medium and label medium for different typography usage.
图 8. 用于不同排版用法的正文大、正文中和标签中。

您的产品可能不需要 Material Design 类型比例中的所有 15 种默认样式。在这个例子中,选择了五种尺寸来创建一个简化的集合,而其他尺寸被省略了。

您可以通过更改 TextStyle字体相关 属性(例如 fontFamilyletterSpacing)的默认值来自定义排版。

bodyLarge = TextStyle(
    fontWeight = FontWeight.Normal,
    fontFamily = FontFamily.SansSerif,
    fontStyle = FontStyle.Italic,
    fontSize = 16.sp,
    lineHeight = 24.sp,
    letterSpacing = 0.15.sp,
    baselineShift = BaselineShift.Subscript
),

定义完 Typography 后,将其传递给 M3 的 MaterialTheme

MaterialTheme(
    typography = replyTypography,
) {
    // M3 app Content
}

使用文本样式

您可以使用 MaterialTheme.typography 获取提供给 M3 的 MaterialTheme 可组合项的排版

Text(
    text = "Hello M3 theming",
    style = MaterialTheme.typography.titleLarge
)
Text(
    text = "you are learning typography",
    style = MaterialTheme.typography.bodyMedium
)

您可以详细了解 Material 指南中关于 应用排版 的内容。

形状

Material 表面可以以不同的形状显示。形状引导注意力、识别组件、传达状态并表达品牌。

形状比例定义了容器角的样式,提供了一系列圆度,从方形到完全圆形。

定义形状

Compose 提供了 M3 的 Shapes 类,它包含扩展的参数以支持新的 M3 形状。M3 形状比例更像 类型比例,它支持在整个 UI 中表达各种形状。

形状有不同的尺寸

  • 特小
  • 特大

默认情况下,每个形状都有一个默认值,但您可以覆盖这些值

val replyShapes = Shapes(
    extraSmall = RoundedCornerShape(4.dp),
    small = RoundedCornerShape(8.dp),
    medium = RoundedCornerShape(12.dp),
    large = RoundedCornerShape(16.dp),
    extraLarge = RoundedCornerShape(24.dp)
)

定义完 Shapes 后,您可以将其传递给 M3 的 MaterialTheme

MaterialTheme(
    shapes = replyShapes,
) {
    // M3 app Content
}

使用形状

您可以在 MaterialTheme 中为所有组件自定义形状比例,也可以在每个组件的基础上进行自定义。

应用带有默认值的中等和大型形状

Card(shape = MaterialTheme.shapes.medium) { /* card content */ }
FloatingActionButton(
    shape = MaterialTheme.shapes.large,
    onClick = {
    }
) {
    /* fab content */
}

Medium shape for Card and Large shape for Floating action button in Reply sample app.
图 9. Reply 示例应用程序中卡片的中等形状和浮动操作按钮的大型形状

还有另外两种形状 - RectangleShapeCircleShape - 是 Compose 的一部分。矩形形状没有边框半径,圆形形状显示完全圆形的边缘

Card(shape = RectangleShape) { /* card content */ }
Card(shape = CircleShape) { /* card content */ }

以下示例演示了一些应用了默认形状值的组件

Default shapes values for all Material 3 components.
图 10. 所有 Material 3 组件的默认形状值。

您可以详细了解 Material 指南中关于 应用形状 的内容。

强调

M3 中的强调使用颜色及其 on-color 组合的变体来提供。在 M3 中,有两种方法可以为 UI 添加强调效果

  • 使用 surface、surface-variant 和 background 以及来自扩展的 M3 颜色系统的 on-surface、on-surface-variants 颜色。例如,surface 可以与 on-surface-variant 一起使用,surface-variant 可以与 on-surface 一起使用,以提供不同级别的强调效果。
Using neutral color combinations for emphasis.
图 11. 使用中性颜色组合进行强调。
  • 使用不同的字体粗细来进行文本强调。如上所述,您可以为类型比例提供自定义粗细,以提供不同的强调效果。

bodyLarge = TextStyle(
    fontWeight = FontWeight.Bold
),
bodyMedium = TextStyle(
    fontWeight = FontWeight.Normal
)

海拔

Material 3 主要使用色调颜色叠加来表示海拔。这是一种区分容器和表面的新方法 - 增加色调海拔使用更突出的色调 - 除了阴影之外。

Tonal elevation with shadow elevation
图 12. 带有阴影 elevationE 的色调海拔

深色主题中的海拔叠加在 Material 3 中也已更改为色调颜色叠加。叠加颜色来自主要颜色槽。

Shadow elevation vs Tonal elevation in Material Design 3
图 13. Material Design 3 中的阴影海拔与色调海拔的对比

M3 的 Surface 是大多数 M3 组件背后的支持性可组合组件,它支持色调和阴影提升。

Surface(
    modifier = Modifier,
    tonalElevation = /*...
    shadowElevation = /*...
) {
    Column(content = content)
}

Material 组件

Material Design 提供了一套丰富的 Material 组件(如按钮、芯片、卡片、导航栏),它们已遵循 Material 主题,可帮助您制作精美的 Material Design 应用程序。您可以立即开始使用具有默认属性的组件。

Button(onClick = { /*..*/ }) {
    Text(text = "My Button")
}

M3 提供了同一组件的多个版本,可根据重点和关注度在不同的角色中使用。

Button emphasis from FAB, Primary down to Text button
图 14. 从 FAB、Primary 到文本按钮的按钮重点
  • 扩展浮动操作按钮,用于最高重点操作

ExtendedFloatingActionButton(
    onClick = { /*..*/ },
    modifier = Modifier
) {
    Icon(
        imageVector = Icons.Default.Edit,
        contentDescription = stringResource(id = R.string.edit),
    )
    Text(
        text = stringResource(id = R.string.add_entry),
    )
}

  • 填充按钮,用于高重点操作

Button(onClick = { /*..*/ }) {
    Text(text = stringResource(id = R.string.view_entry))
}

  • 文本按钮,用于低重点操作

TextButton(onClick = { /*..*/ }) {
    Text(text = stringResource(id = R.string.replated_articles))
}

您可以详细了解 Material 按钮和其他组件。Material 3 提供了各种组件套件,例如按钮、应用程序栏、导航组件,它们专门为不同的用例和屏幕尺寸而设计。

Material 还提供了一些导航组件,可帮助您根据不同的屏幕尺寸和状态实现导航。

NavigationBar 用于紧凑型设备,当您想要定位 5 个或更少的目的地时

NavigationBar(modifier = Modifier.fillMaxWidth()) {
    Destinations.entries.forEach { replyDestination ->
        NavigationBarItem(
            selected = selectedDestination == replyDestination,
            onClick = { },
            icon = { }
        )
    }
}

NavigationRail 用于小型到中型平板电脑或横屏手机。它为用户提供了人体工程学,并改善了这些设备的用户体验。

NavigationRail(
    modifier = Modifier.fillMaxHeight(),
) {
    Destinations.entries.forEach { replyDestination ->
        NavigationRailItem(
            selected = selectedDestination == replyDestination,
            onClick = { },
            icon = { }
        )
    }
}

Reply Showcase of BottomNavigationBar(Left) and NavigationRail(Right)
图 15. 回复展示了 BottomNavigationBar(左)和 NavigationRail(右)

回复在默认主题中同时使用这两个组件,为所有设备尺寸提供沉浸式用户体验。

NavigationDrawer 用于中型到大型平板电脑,您有足够的空间来显示详细信息。您可以同时使用 PermanentNavigationDrawerModalNavigationDrawer 以及 NavigationRail

PermanentNavigationDrawer(modifier = Modifier.fillMaxHeight(), drawerContent = {
    Destinations.entries.forEach { replyDestination ->
        NavigationRailItem(
            selected = selectedDestination == replyDestination,
            onClick = { },
            icon = { },
            label = { }
        )
    }
}) {
}

Reply Showcase of Permanent navigation drawer
图 16. 回复展示了永久导航抽屉

导航选项增强了用户体验、人体工程学和可达性。您可以在 Compose 自适应代码实验室 中详细了解 Material 导航组件。

自定义组件的主题

M3 鼓励个性化和灵活性。所有组件都应用了默认颜色,但会公开灵活的 API,以便在需要时自定义其颜色。

大多数组件,如卡片和按钮,都提供了一个默认对象,该对象公开了颜色和提升接口,可以修改这些接口以自定义组件

val customCardColors = CardDefaults.cardColors(
    contentColor = MaterialTheme.colorScheme.primary,
    containerColor = MaterialTheme.colorScheme.primaryContainer,
    disabledContentColor = MaterialTheme.colorScheme.surface,
    disabledContainerColor = MaterialTheme.colorScheme.onSurface,
)
val customCardElevation = CardDefaults.cardElevation(
    defaultElevation = 8.dp,
    pressedElevation = 2.dp,
    focusedElevation = 4.dp
)
Card(
    colors = customCardColors,
    elevation = customCardElevation
) {
    // m3 card content
}

您可以详细了解 自定义 Material 3

系统 UI

Material You 的某些方面来自 Android 12 及更高版本上的新视觉风格和系统 UI。有两个主要领域发生了变化:涟漪和滚动过度。

涟漪

涟漪现在使用微妙的闪光来照亮按下时的表面。Compose Material Ripple 在 Android 上使用平台 RippleDrawable,因此闪光涟漪在 Android 12 及更高版本上可用于所有 Material 组件。

Ripple in M2 vs M3
图 17. M2 中的涟漪与 M3 中的涟漪

滚动过度

滚动过度现在在滚动容器边缘使用 拉伸效果。在 Compose Foundation 1.1.0 及更高版本中,滚动容器可组合组件(例如,LazyColumnLazyRowLazyVerticalGrid)中,无论 API 级别如何,拉伸滚动过度都是默认启用的。

Overscroll using stretch effect at the edge of the container
图 18. 在容器边缘使用拉伸效果的滚动过度

辅助功能

内置于 Material 组件中的辅助功能标准旨在为包容性产品设计提供基础。了解产品的辅助功能可以增强所有用户的可用性,包括有视力障碍、失明、听力障碍、认知障碍、运动障碍或情景性残疾(例如骨折)的用户。

颜色辅助功能

动态颜色旨在满足颜色对比度的辅助功能标准。色调调色板系统对于默认情况下使任何颜色方案都可访问至关重要。

Material 的颜色系统提供了标准的色调值和度量,可用于满足可访问的对比度比率。

Reply sample app: Primary, secondary and tertiary tonal pallets (top to bottom)
图 19. 回复示例应用程序:主要、次要和三级色调调色板(从上到下)

所有 Material 组件和动态主题都已从一组 色调调色板 中使用了上述颜色角色,这些调色板经过选择以满足辅助功能要求。但是,如果您要自定义组件,请确保使用适当的颜色角色并避免不匹配。

在主要颜色之上使用 on-primary,在主要颜色容器之上使用 on-primary-container,对于其他强调色和中性色也一样,为用户提供可访问的对比度。

在主要颜色之上使用三级颜色容器会为用户提供对比度不佳的按钮

// ✅ Button with sufficient contrast ratio
Button(
    onClick = { },
    colors = ButtonDefaults.buttonColors(
        containerColor = MaterialTheme.colorScheme.primary,
        contentColor = MaterialTheme.colorScheme.onPrimary
    )
) {
}

// ❌ Button with poor contrast ratio
Button(
    onClick = { },
    colors = ButtonDefaults.buttonColors(
        containerColor = MaterialTheme.colorScheme.tertiaryContainer,
        contentColor = MaterialTheme.colorScheme.primaryContainer
    )
) {
}

Sufficient contrast (left) vs Poor contrast (right)
图 20. 足够的对比度(左)与对比度不佳(右)

排版辅助功能

M3 类型比例更新了静态类型斜坡和值,以提供一个简化但动态的尺寸类别框架,这些类别可以在设备之间缩放。

例如,在 M3 中,显示小型可以根据设备上下文(例如手机或平板电脑)分配不同的值。

大屏幕

Material 提供了有关自适应布局和可折叠设备的指南,以使您的应用程序可访问并改善用户握持大型设备的人体工程学。

Material 提供了不同类型的 导航,可帮助您为大型设备提供更好的用户体验。

您可以详细了解 Android 大屏幕应用程序质量指南,并查看我们的 回复示例,以了解自适应和可访问的设计。

了解更多

要详细了解 Compose 中的 Material 主题,请查看以下资源

示例应用程序

文档

API 参考和源代码

视频