Android开发 Material Design 3 实践
引言
Material Design 3(简称MD3)作为Google推出的最新设计系统,在Material Design 2的基础上进行了全面升级。从更灵动的动态色彩系统,到更灵活的组件样式,再到与Jetpack Compose的深度融合,给Android开发体验带来了不少新鲜感。对于Android开发者而言,掌握MD3不仅能提升应用的视觉体验,还能简化UI开发流程、增强应用的一致性。今天就想结合自己的实践,从MD3的核心特性出发,结合具体的技术实现代码,对MD3开发进行探索。
一、MD3 核心特性概览
在开始之前,先简单聊聊MD3最吸引我的几个核心特性——也是在实践项目中重点用到的功能:
-
动态色彩系统:这个功能能自动从用户的壁纸中提取主色调,生成一套和壁纸风格匹配的色彩方案,包括主色、辅助色、中性色等。使用了这个功能后,应用和用户的设备主题几乎融为一体,提升了视觉体验。
-
增强型组件:更新了Button、Card、Dialog、Navigation等核心组件的样式,支持更多自定义属性,同时新增了如BottomSheetScaffold、NavigationRail等实用组件。
-
Typography 优化:MD3的字体系统更清晰了,不仅有明确的层级,还支持动态调整字体大小,对不同屏幕尺寸和有特殊需求的用户都更友好。
-
Jetpack Compose 深度集成:MD3为Compose提供了专属的组件库,相比XML布局,Compose能更高效地实现MD3的设计理念。
二、环境准备:接入 MD3 依赖
接下来聊聊接入MD3的实际操作。不管是用传统的XML布局还是Jetpack Compose,第一步都是添加依赖。这里建议使用AndroidX的官方库,兼容性和稳定性都更有保障。
2.1 基础依赖添加
如果你已经在使用Jetpack Compose开发,接入MD3就很简单了,只需要在模块级的build.gradle文件中添加几个依赖即可。
// 模块级 build.gradle
dependencies {
// 使用 Compose BOM 来管理版本
implementation platform('androidx.compose:compose-bom:2024.06.00')
// MD3 Compose 核心组件库
implementation 'androidx.compose.material3:material3'
// MD3 图标库
implementation 'androidx.compose.material:material-icons-core'
implementation 'androidx.compose.material:material-icons-outlined'
// 动态色彩支持
implementation 'androidx.compose.material3:material3-dynamic-color'
// 其他 Compose 依赖
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.activity:activity-compose:1.8.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation platform('androidx.compose:compose-bom:2024.06.00')
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
debugImplementation 'androidx.compose.ui:ui-tooling'
debugImplementation 'androidx.compose.ui:ui-test-manifest'
}
// 确保 Compose 编译器版本与 Kotlin 版本匹配
composeOptions {
kotlinCompilerExtensionVersion "1.5.4"
}
kotlinOptions {
jvmTarget = "1.8"
}
三、核心技术实现
下面将围绕MD3的核心特性,讲解Compose的实现方案,重点聚焦于动态色彩、核心组件和主题定制。
3.1 动态色彩系统实现
动态色彩是MD3的标志性特性,它允许应用从用户设备的壁纸中提取颜色信息,生成与壁纸色调协调的个性化色彩方案。这种设计理念能让应用与用户的设备主题更加融合,提供更具沉浸感的视觉体验。实现动态色彩需要借助 material3-dynamic-color 库,这个库封装了系统级的色彩提取API,简化了开发者的实现工作。
动态色彩系统通过分析壁纸的主色调、辅助色和中性色,自动生成符合MD3设计规范的色彩方案。不过有一点要注意:这个功能只在Android 12及以上版本才能用,所以得做好旧版本的兼容处理。
3.1.1 创建MD3主题
首先,需要在 Theme.kt 文件中定义MD3主题,支持动态色彩和主题切换。MD3主题基于Material Design 3规范,提供了现代、灵活的设计系统。
package com.example.md3demo.ui.theme
静态色彩方案定义:
静态色彩方案作为动态色彩的备选方案,确保在不支持动态色彩的设备上应用仍能保持一致的视觉风格。我们需要分别定义亮色和暗色主题的色彩方案:
// 暗色主题色彩方案
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
// 亮色主题色彩方案
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
)
浅色主题实现效果如下

深色主题实现效果如下

MD3主题函数实现:
这个函数是整个应用主题的核心,它会根据系统版本、用户的主题偏好和动态色彩开关,智能地选择最合适的色彩方案。
@Composable
fun MD3DemoTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true, // 动态色彩仅在Android 12+可用
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
技术实现细节:
- 使用
when表达式实现条件分支选择,首先检查是否支持动态色彩 dynamicDarkColorScheme和dynamicLightColorScheme是MD3提供的动态色彩生成函数,能从设备壁纸中提取颜色darkColorScheme和lightColorScheme定义静态色彩方案,作为动态色彩的备选
这种分层的主题实现方式非常灵活,既支持了最新的动态色彩特性,又兼容了旧版本系统。通过将主题逻辑封装在一个函数中,可以在应用的任何地方方便地使用统一的主题样式。
最后一步,就是把MD3主题应用到整个应用内容上:
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
技术说明:
MaterialTheme是Compose中应用主题的核心组件,它接收colorScheme(色彩方案)、typography(排版)等参数Typography是在Theme.kt中定义的MD3排版样式,包含了标题、正文、按钮等不同文本元素的样式定义content参数是一个Composable函数,代表应用的所有内容,主题会应用到这个内容上
MaterialTheme就像是一个“主题容器”,我们把之前生成的colorScheme(色彩方案)和定义好的Typography(排版样式)传进去,它就会自动把这些样式应用到里面的所有内容上。这里的content参数就是我们整个应用的UI内容,用Composable函数的形式传递进来。
浅色模式下的系统主要色彩

发现应用中的按钮文字等应用到了动态色彩主题方案

深色模式下的系统主要色彩

同样也应用到了动态色彩主题方案

3.2 主题切换功能实现
主题切换是现代应用的基本需求,MD3对主题切换的支持非常完善,结合Jetpack Compose的状态管理,实现起来既简单又灵活。
3.2.1 状态管理与主题参数传递
在Compose中实现主题切换,最核心的就是状态管理。我们需要创建一些状态变量来控制主题的各种参数,然后把这些状态传递给主题函数。我们需要在应用的入口点 MainActivity.kt 中管理这些主题状态,这样整个应用都能方便地访问和修改它们。
技术实现要点:
- 使用
remember和mutableStateOf创建可持久化、可观察的状态变量 - 状态变量需要在应用的顶层管理,以便在整个应用范围内共享
- 主题状态通过参数传递给MD3主题函数,实现主题的动态切换
关键状态变量:
isDarkTheme: 控制应用是亮色主题还是暗色主题isDynamicColorEnabled: 控制是否启用MD3的动态色彩功能
这两个变量都用 remember 包装了一下,这样在Compose重组时它们的值就不会丢失,能保持状态的一致性。
3.2.2 MainActivity的主题管理实现
下面是具体的实现代码。在MainActivity的onCreate方法中,是这样管理主题状态的:
首先创建两个关键的状态变量,一个控制亮色/暗色主题切换,另一个控制动态色彩的开关。这两个变量都用 remember 包装了一下,确保在Compose重组时状态不会丢失。然后把这些状态作为参数传递给我们之前定义的MD3DemoTheme函数,这样就能实现主题的动态切换了。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
// 主题状态管理:使用remember确保状态在重组时保持不变
val isDarkTheme = remember { mutableStateOf(false) } // 默认使用亮色主题
val isDynamicColorEnabled = remember { mutableStateOf(true) } // 默认启用动态色彩
MD3DemoTheme(
darkTheme = isDarkTheme.value,
dynamicColor = isDynamicColorEnabled.value
) {
MD3BottomNavigationScreen(isDarkTheme, isDynamicColorEnabled)
}
}
}
}
这里的实现其实体现了Compose中一个很重要的原则——“状态提升”。简单来说,就是把主题状态放到应用的顶层组件(这里是MainActivity)中统一管理,然后通过参数把状态传递给需要用到它的子组件。这样做有两个好处:一是能确保整个应用的主题状态保持一致,二是让主题切换的逻辑更加集中,方便后续维护。
在Compose中实现主题切换时,状态管理是核心。使用 remember 和 mutableStateOf 创建的状态变量,能够在UI重组时自动更新相关组件,这是Compose声明式UI的一大优势。相比传统的XML布局+样式的方式,Compose的主题切换实现更加简洁和直观。
接下来,咱们来实现一个优化后的MD3动态色彩示例页面。这个页面主要展示了主题切换和动态色彩的核心功能,界面简洁明了,交互也很直观:
// MD3 动态色彩示例页面核心实现
@Composable
fun MD3DynamicColorScreen(
isDarkTheme: MutableState<Boolean>,
isDynamicColorEnabled: MutableState<Boolean>
) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
// 主题控制区域
Text("主题控制", style = MaterialTheme.typography.titleMedium)
// 浅色/深色主题切换按钮
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
Button(onClick = { isDarkTheme.value = false }) {
Text("浅色主题")
}
Button(onClick = { isDarkTheme.value = true }) {
Text("深色主题")
}
}
// 动态色彩开关按钮
Button(
onClick = { isDynamicColorEnabled.value = !isDynamicColorEnabled.value },
colors = ButtonDefaults.buttonColors(
containerColor = if (isDynamicColorEnabled.value)
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.secondary
)
) {
Text(if (isDynamicColorEnabled.value) "动态色彩: 开启" else "动态色彩: 关闭")
}
// 主题状态指示器
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant
)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text("当前主题状态", style = MaterialTheme.typography.titleMedium)
Text("主题模式: ${if (isDarkTheme.value) "深色" else "浅色"}")
Text("动态色彩: ${if (isDynamicColorEnabled.value) "已开启" else "已关闭"}")
}
}
}
}
深浅主题切换

动态色彩应用到主题切换

3.3 自定义 FAB 实现
Floating Action Button (FAB) 是MD3中用于触发应用主要操作的核心交互组件,通常以圆形按钮的形式悬浮在界面右下角。MD3的FAB支持高度自定义,包括尺寸(标准/迷你/扩展)、颜色、形状和内容。
在Jetpack Compose中,FAB通过FloatingActionButton组件实现,它继承了MD3的设计规范,默认使用主题的主色作为背景色,并提供了丰富的自定义参数。FAB的核心设计理念是突出显示应用的最重要操作,如"添加"、"创建"或"发送"等。
使用场景:FAB适合在需要突出显示主要操作的界面中使用,例如:
- 待办事项应用中的"添加任务"按钮
- 社交媒体应用中的"发布动态"按钮
- 邮件应用中的"写邮件"按钮
让我们通过具体代码实现来了解如何自定义FAB:
首先,先创建一个组件示例页面的基本结构:
// MD3 组件示例页面
@Composable
fun MD3ComponentsScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
// FAB 演示区域
Text(text = "MD3 浮动操作按钮", style = MaterialTheme.typography.titleMedium)
Column(
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// 不同尺寸的FAB将在这里实现
}
}
}
接下来,实现不同尺寸的FAB。MD3提供了三种主要尺寸的FAB:标准尺寸(56dp)、迷你尺寸(40dp)和扩展尺寸(包含文本和图标)。我们可以通过modifier参数自定义FAB的大小:
// 不同尺寸的FAB
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
FloatingActionButton(
onClick = { }
) {
Icon(
imageVector = Icons.Outlined.Add,
contentDescription = "添加"
)
}
// 小尺寸FAB(40dp):适合次要操作或空间有限的场景
FloatingActionButton(
onClick = { },
shape = RoundedCornerShape(16.dp),
modifier = Modifier.size(40.dp)
) {
Icon(
imageVector = Icons.Outlined.Edit,
contentDescription = "编辑"
)
}
// 大尺寸FAB(60dp):适合需要更强视觉突出的场景
FloatingActionButton(
onClick = { },
shape = RoundedCornerShape(16.dp),
modifier = Modifier.size(60.dp)
) {
Icon(
imageVector = Icons.Outlined.Share,
contentDescription = "分享"
)
}
}
技术实现细节:
- 默认尺寸FAB不需要额外设置size,系统会自动使用标准的56dp大小
- 通过
Modifier.size()可以精确控制FAB的尺寸 - 使用
shape参数可以自定义FAB的形状,MD3建议使用圆角矩形,圆角半径根据尺寸调整 - 迷你尺寸FAB通常用于次要操作,如在列表中编辑单个项目
- 扩展尺寸FAB(示例中未展示)结合了图标和文本,提供更明确的操作提示
实现效果如下

最后,实现自定义颜色的FAB。在MD3中,FAB默认使用主题的主色(primary)作为背景色,但我们可以通过containerColor参数自定义背景色,并通过contentColor或图标组件的tint参数自定义图标颜色:
// 自定义颜色的FAB
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
// 使用主题辅助色(secondary)的FAB
FloatingActionButton(
onClick = { },
containerColor = MaterialTheme.colorScheme.secondary // 自定义背景色为辅助色
) {
Icon(
imageVector = Icons.Outlined.Favorite,
contentDescription = "收藏",
tint = MaterialTheme.colorScheme.onSecondary // 图标颜色使用与辅助色匹配的文本色
)
}
// 使用主题第三色(tertiary)的FAB
FloatingActionButton(
onClick = { },
containerColor = MaterialTheme.colorScheme.tertiary // 自定义背景色为第三色
) {
Icon(
imageVector = Icons.Outlined.Warning,
contentDescription = "提醒",
tint = MaterialTheme.colorScheme.onTertiary // 图标颜色使用与第三色匹配的文本色
)
}
}
实现效果如下

技术实现细节:
containerColor参数用于设置FAB的背景颜色- 为了确保图标与背景色的对比度符合可访问性标准,应使用与背景色对应的文本色(如
onSecondary、onTertiary) - 自定义颜色的FAB适合用于强调特定类型的操作,如"收藏"、"提醒"等
- 在动态色彩模式下,自定义颜色的FAB也会随主题自动调整,保持整体的视觉一致性
在实际项目中,建议尽量使用MD3色彩系统里定义好的颜色变量(比如primary、secondary、tertiary),而不是直接写死颜色值。这样做有两个好处:一是能保证应用风格的统一性,二是在切换主题或开启动态色彩时,FAB的颜色也能自动适配,不用额外写太多适配代码。
3.4 交互式卡片组件实现
MD3的卡片组件是界面设计中的核心元素,它不仅提供了视觉上的层次感,还支持丰富的交互效果。在Compose里做交互式卡片很方便,像点击反馈、展开收起、点击计数这些功能,用状态管理和组件属性的动态变化能轻松实现。
3.4.1 交互式卡片
首先,实现一个具有点击反馈效果的交互式卡片——当用户点击它时,卡片的背景色会变化,阴影也会变深,这样用户就能清楚地知道自己进行了点击。
// MD3 交互式卡片组件
@Composable
fun InteractiveCard() {
// 点击状态管理
val isClicked = remember { mutableStateOf(false) }
Card(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
.clickable {
isClicked.value = !isClicked.value
},
// 根据点击状态动态调整阴影高度和背景色
elevation = if (isClicked.value) CardDefaults.cardElevation(8.dp) else CardDefaults.cardElevation(4.dp),
colors = CardDefaults.cardColors(
containerColor = if (isClicked.value) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surface
)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = "交互式卡片",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface
)
Text(
text = "点击此卡片查看交互效果",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 8.dp)
)
if (isClicked.value) {
// 点击后显示额外内容
Text(
text = "卡片已被点击!",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(top = 8.dp)
)
}
}
}
}
实现效果如下

3.4.2 可展开卡片
接下来,实现一个支持展开/收起功能的可展开卡片。这种卡片在内容较多但又不需要一次性全部展示时非常有用,比如商品详情、文章摘要等场景。
// MD3 可展开卡片组件
@Composable
fun ExpandableCard() {
// 展开状态管理
val isExpanded = remember { mutableStateOf(false) }
Card(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
.clickable {
isExpanded.value = !isExpanded.value
},
elevation = CardDefaults.cardElevation(4.dp)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
// 卡片标题和展开/收起图标
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "可展开卡片",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface
)
Icon(
// 根据展开状态显示不同的图标
imageVector = if (isExpanded.value) Icons.Outlined.ExpandLess else Icons.Outlined.ExpandMore,
contentDescription = if (isExpanded.value) "收起" else "展开"
)
}
Text(
text = "点击此卡片展开或收起详细内容",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 8.dp)
)
// 展开时显示的详细内容
if (isExpanded.value) {
Column(
modifier = Modifier.padding(top = 16.dp)
) {
Text(
text = "详细内容标题",
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.onSurface
)
Text(
text = "这是卡片展开后的详细内容,您可以在这里放置更多的文本、图片或其他组件。MD3的卡片组件提供了丰富的自定义选项。",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 8.dp)
)
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier.fillMaxWidth()
) {
TextButton(onClick = { }) {
Text("查看更多")
}
}
}
}
}
}
}
实现效果如下

3.4.3 点击计数卡片
最后是一个有趣的小组件——点击计数卡片。这个组件特别适合用来做原型设计,比如模拟点赞、收藏、投票这类需要用户交互计数的功能,它能简单直观又能快速展示交互效果。
// MD3 点击计数卡片组件
@Composable
fun ClickCounterCard() {
// 点击计数状态管理
val clickCount = remember { mutableStateOf(0) }
Card(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
.clickable {
clickCount.value++
},
elevation = CardDefaults.cardElevation(4.dp)
) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "点击计数卡片",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface
)
Text(
text = "点击此卡片增加计数",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 8.dp)
)
// 计数显示区域
Box(
modifier = Modifier
.size(60.dp)
.background(MaterialTheme.colorScheme.primaryContainer) // 使用主题的主容器色
.clip(RoundedCornerShape(30.dp)) // 圆形显示
.padding(top = 16.dp),
contentAlignment = Alignment.Center
) {
Text(
text = clickCount.value.toString(),
style = MaterialTheme.typography.headlineMedium, // 大字体显示计数
color = MaterialTheme.colorScheme.onPrimaryContainer // 使用与容器色对比的文本色
)
}
Text(
text = "点击次数: ${clickCount.value}",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 8.dp)
)
}
}
}
技术实现细节:
- 使用
remember { mutableStateOf(0) }来管理点击计数,remember确保状态在重组时不会丢失,mutableStateOf使状态变化能触发UI重组 - 点击事件通过
clickable修饰符实现,每次点击将计数加1 - 计数显示区域使用
Box组件实现圆形背景,通过RoundedCornerShape(30.dp)实现圆形效果 - 颜色搭配遵循MD3设计规范,使用主题的
primaryContainer和onPrimaryContainer确保良好的对比度和视觉效果
实现效果如下

在实现这类交互式卡片时,状态管理是核心。Compose的响应式状态系统让UI与状态的同步变得非常简单,开发者只需关注状态的变化逻辑,而不需要手动更新UI。这种声明式的编程模式相比传统的命令式UI开发,大大减少了代码量和潜在的bug。
3.5 其他常用MD3组件
MD3的组件库很丰富,除了刚才的卡片和FAB,还有很多日常开发中常用的组件。接下来再尝试几个最常用的——按钮、开关、滑块、进度条和文本输入框,这些组件几乎是每个Android应用都会用到的。
3.5.1 MD3按钮组件
首先,我们来实现MD3的三种按钮样式:填充按钮、轮廓按钮和文本按钮:
// MD3 按钮组件
@Composable
fun MD3Buttons() {
Column(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Button(onClick = { }) {
Text(text = "填充按钮")
}
OutlinedButton(onClick = { }) {
Text(text = "轮廓按钮")
}
TextButton(onClick = { }) {
Text(text = "文本按钮")
}
}
}
实现效果如下

3.5.2 MD3开关组件
然后是开关组件,这个在设置页面里特别常用。MD3的开关设计得很精致,选中和未选中状态的视觉对比清晰,用户一眼就能明白当前状态:
// MD3 开关组件
@Composable
fun MD3Switch() {
val switchState = remember { mutableStateOf(false) }
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth().padding(8.dp)
) {
Text(text = "开关示例", style = MaterialTheme.typography.bodyMedium)
Switch(
checked = switchState.value,
onCheckedChange = { switchState.value = it },
interactionSource = remember { MutableInteractionSource() },
thumbContent = if (switchState.value) {
{ Icon(Icons.Outlined.Check, contentDescription = null) }
} else null
)
}
}
实现效果如下

3.5.3 MD3滑块组件
接下来是滑块组件,这个适合需要用户选择一个范围内的值的场景,比如音量调节、亮度控制、图片裁剪等:
// MD3 滑块组件
@Composable
fun MD3Slider() {
val sliderValue = remember { mutableStateOf(0.5f) }
Column(modifier = Modifier.padding(8.dp)) {
Slider(
value = sliderValue.value,
onValueChange = { sliderValue.value = it },
valueRange = 0f..1f,
modifier = Modifier.fillMaxWidth()
)
Text(
text = "当前值: ${(sliderValue.value * 100).toInt()}%",
style = MaterialTheme.typography.bodySmall,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
}
实现效果如下

3.5.4 MD3进度条组件
进度条也是必不可少的组件,特别是在加载数据或者执行耗时操作的时候。MD3提供了两种常用的进度条样式:线性和圆形:
// MD3 进度条组件
@Composable
fun MD3ProgressIndicators() {
Column(
verticalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.padding(8.dp)
) {
LinearProgressIndicator(
progress = { 0.75f },
modifier = Modifier.fillMaxWidth()
)
CircularProgressIndicator(
progress = { 0.5f },
modifier = Modifier.size(48.dp).align(Alignment.CenterHorizontally)
)
}
}
实现效果如下

3.5.5 MD3文本输入框
现在,实现MD3的文本输入框,支持标签、占位符和图标,用户登录、注册、搜索、填写表单都离不开它:
// MD3 文本输入框
@Composable
fun MD3TextField() {
val textFieldValue = remember { mutableStateOf("") }
TextField(
value = textFieldValue.value,
onValueChange = { textFieldValue.value = it },
label = { Text("输入框示例") },
placeholder = { Text("请输入内容") },
leadingIcon = { Icon(Icons.Outlined.Info, contentDescription = null) },
trailingIcon = { Icon(Icons.Outlined.ChevronRight, contentDescription = null) },
modifier = Modifier.fillMaxWidth().padding(8.dp)
)
}
实现效果如下

3.5.6 页面垂直滚动
现在,我们将所有MD3组件整合到一个页面中,方便统一展示。由于组件数量较多,为了确保所有内容都能完整显示,我们需要为页面添加垂直滚动功能。
在Jetpack Compose中,实现垂直滚动需要使用verticalScroll修饰符,并配合rememberScrollState()来创建一个持久的滚动状态。rememberScrollState()会记住用户的滚动位置,确保在UI重组时不会丢失滚动状态。
// MD3 组件页面 - 核心滚动功能
@Composable
fun MD3ComponentsScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()) // 启用垂直滚动
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
// 这里是具体组件内容
}
}
实现效果如下

添加滚动功能后,用户可以通过垂直滑动查看页面上的所有组件,解决了内容过长导致部分组件无法显示的问题。这种实现方式简单高效,是Jetpack Compose中处理长列表和长页面的常用方法。
3.6 底部导航栏实现
底部导航栏是移动应用中常用的导航模式,它能让用户快速在不同模块之间切换。MD3对底部导航栏进行了重新设计,样式更现代,交互体验也更好。
在Compose里实现底部导航栏较方便,只需要用Scaffold组件配合BottomAppBar,代码结构清晰。
3.6.1 底部导航屏幕的基本结构
首先,创建底部导航屏幕的基本结构和状态管理。这个屏幕将作为应用的主界面,包含底部导航栏和根据选中项动态切换的内容区域。
// MD3 底部导航屏幕
@Composable
fun MD3BottomNavigationScreen(
// 从父组件传递的主题状态
isDarkTheme: MutableState<Boolean>,
isDynamicColorEnabled: MutableState<Boolean>
) {
// 当前选中的导航项索引
val selectedItemIndex = remember { mutableStateOf(0) }
3.6.2 定义导航项数据
接下来,定义导航栏的四个标签页数据。我们使用一个列表来存储每个导航项的标题和图标,这样可以方便地遍历生成导航按钮。
val navItems = listOf(
"首页" to Icons.Outlined.Home,
"组件" to Icons.Outlined.Build,
"设置" to Icons.Outlined.Settings,
"关于" to Icons.Outlined.Info
)
技术实现细节:
- 使用
listOf创建不可变列表,存储导航项的标题(String类型)和图标(ImageVector类型) - 采用
to关键字创建键值对,使代码更加简洁易读 - 选择合适的图标有助于用户直观理解每个导航项的功能
3.6.3 实现底部导航栏
最后,使用Scaffold组件实现底部导航栏。Scaffold是Compose中用于构建具有基本布局结构的组件,它提供了顶部应用栏、底部应用栏、浮动操作按钮等插槽。
// 使用Scaffold构建页面结构
Scaffold(
bottomBar = {
BottomAppBar {
// 遍历导航项列表,生成导航按钮
navItems.forEachIndexed { index, (title, icon) ->
NavigationBarItem(
selected = selectedItemIndex.value == index,
onClick = { selectedItemIndex.value = index },
icon = { Icon(imageVector = icon, contentDescription = title) },
label = { Text(text = title) },
alwaysShowLabel = false // 未选中时隐藏文本,节省空间
)
}
}
}
) {
innerPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
) {
when (selectedItemIndex.value) {
0 -> MD3DynamicColorScreen(isDarkTheme, isDynamicColorEnabled) // 首页
1 -> MD3ComponentsScreen() // 组件页面
2 -> MD3SettingsScreen() // 设置页面
3 -> MD3AboutScreen() // 关于页面
}
}
}
}
技术实现细节:
Scaffold组件的bottomBar参数用于设置底部应用栏,是MD3应用布局的核心组件BottomAppBar是MD3中专门用于实现底部导航的组件,提供了现代化的导航栏样式NavigationBarItem用于创建单个导航按钮,它会根据selected状态自动应用不同的视觉样式(选中时的高亮效果)- 使用
forEachIndexed遍历导航项列表,动态生成导航按钮,避免重复代码,提高可维护性 Scaffold会自动提供内边距信息(innerPadding参数),我们需要将其应用到内容区域,确保内容不会被底部导航栏遮挡- 使用
Column作为内容容器,确保页面内容能够正常显示和滚动 when表达式用于根据选中的导航项索引动态切换显示的页面组件,实现了页面间的无缝切换
实现效果如下

设计思路与个人心得:
- 采用声明式的方式构建导航结构,代码简洁易读,符合Compose的设计理念
- 利用组件化思想,将导航逻辑与页面内容分离,每个页面都是独立的Composable函数,提高了代码的可维护性和可测试性
alwaysShowLabel = false参数的使用优化了导航栏的空间利用,未选中的导航项只显示图标,选中时才显示文字,使界面更加简洁美观- 状态管理方面,使用
remember { mutableStateOf(0) }来保存选中的导航项索引,确保在组件重组时状态不会丢失,这是Compose中常用的状态管理方式
3.6.4 底部导航的优势与应用场景
底部导航栏是移动应用中最常用的导航模式之一,具有以下优势:
- 易于访问:底部导航栏位于屏幕底部,用户可以轻松用拇指触及,符合人体工程学设计
- 清晰可见:导航项始终可见,用户可以随时了解当前位置和切换到其他页面
- 减少认知负担:有限的导航项数量(通常3-5个)可以帮助用户快速理解应用的主要功能结构
- 一致的用户体验:底部导航是移动应用的常见模式,用户已经熟悉这种导航方式
底部导航栏适用于以下应用场景:
- 具有3-5个核心功能模块的应用
- 需要在不同功能模块间频繁切换的应用
- 希望用户能够快速访问主要功能的应用
3.7 设置页面实现
为了让演示应用更完整,还设计了设置页面,虽然功能简单,但却是一个成熟应用不可或缺的部分:
3.7.1 设置页面 - 基本结构
一个典型的设置页面通常包含各种开关和选项,我在这个页面里实现了几个常见的设置项:字体大小、通知开关、声音开关和振动开关:
// MD3 设置页面
@Composable
fun MD3SettingsScreen() {
// 设置状态
val fontSize = remember { mutableStateOf(2) }
val notificationsEnabled = remember { mutableStateOf(true) }
val soundEnabled = remember { mutableStateOf(true) }
val vibrationEnabled = remember { mutableStateOf(false) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
// 标题
Text(
text = "设置",
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(bottom = 24.dp)
)
3.7.2 设置页面 - 显示设置
接下来,实现显示设置部分,包括字体大小选择:
// 显示设置核心代码
Card {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = "显示设置", style = MaterialTheme.typography.titleMedium)
// 字体大小选择核心逻辑
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
Button(
onClick = { fontSize.value = 0 },
colors = ButtonDefaults.buttonColors(
containerColor = if (fontSize.value == 0) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceVariant
)
) {
Text("小")
}
Button(
onClick = { fontSize.value = 1 },
colors = ButtonDefaults.buttonColors(
containerColor = if (fontSize.value == 1) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceVariant
)
) {
Text("中")
}
Button(
onClick = { fontSize.value = 2 },
colors = ButtonDefaults.buttonColors(
containerColor = if (fontSize.value == 2) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceVariant
)
) {
Text("大")
}
}
}
}
实现效果如下

3.7.3 设置页面 - 通知设置
然后,实现通知设置部分,包括通知总开关、声音开关和振动开关:
// 通知设置核心代码
Card {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = "通知设置", style = MaterialTheme.typography.titleMedium)
// 通知总开关核心逻辑
Row(horizontalArrangement = Arrangement.SpaceBetween) {
Text(text = "启用通知")
Switch(
checked = notificationsEnabled.value,
onCheckedChange = { notificationsEnabled.value = it }
)
}
// 声音开关核心逻辑(依赖总开关)
Row(horizontalArrangement = Arrangement.SpaceBetween) {
Text(text = "通知声音")
Switch(
checked = soundEnabled.value,
onCheckedChange = { soundEnabled.value = it },
enabled = notificationsEnabled.value // 总开关控制子开关可用性
)
}
// 振动开关核心逻辑(依赖总开关)
Row(horizontalArrangement = Arrangement.SpaceBetween) {
Text(text = "通知振动")
Switch(
checked = vibrationEnabled.value,
onCheckedChange = { vibrationEnabled.value = it },
enabled = notificationsEnabled.value // 总开关控制子开关可用性
)
}
}
}
实现效果如下

四、MD3 与 MD2 的核心差异
在迁移或使用MD3时,需注意与MD2的差异,避免踩坑:
| 特性 | Material Design 2 | Material Design 3 |
|---|---|---|
| 色彩系统 | 固定主色、辅助色,无动态色彩 | 支持动态色彩,新增中性色层级 |
| 组件样式 | 按钮、卡片等组件样式单一 | 组件样式更丰富,支持更多自定义属性 |
| 排版 | 字体层级较少,无动态字体适配 | 完善的字体层级,支持动态字体大小 |
| Compose 支持 | 支持有限 | 深度集成,专属组件库 |
五、实践建议与注意事项
-
优先使用 Compose:MD3 对 Compose 的支持更完善,开发效率更高,推荐新项目或迁移项目使用 Compose 实现 MD3 设计。
-
兼容性处理:动态色彩仅支持 Android 12+,需为低版本设计默认色彩方案;组件样式需适配不同屏幕尺寸和分辨率。
-
主题一致性:整个应用应统一使用 MD3 主题,避免混合 MD2 和 MD3 组件,确保视觉风格一致。
-
可访问性优化:利用 MD3 的动态字体、高对比度色彩等特性,提升应用的可访问性,让应用对视力障碍用户更友好。
-
版本更新:MD3 库仍在持续迭代,建议关注官方文档,及时更新依赖版本,获取最新特性和bug修复。
六、总结
本文详细介绍了 Material Design 3 的核心特性和实现方法,包括动态色彩系统、主题定制、组件实现和导航架构等方面。通过结合 Jetpack Compose,我们可以高效地实现 MD3 的设计理念,创建出视觉吸引力强、交互友好的现代 Android 应用。
在实际开发中,建议根据项目需求选择合适的组件和功能,并结合官方文档和设计规范进行开发,以确保应用的质量和用户体验。

1275

被折叠的 条评论
为什么被折叠?



