Android开发 Material Design 3 实践

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 表达式实现条件分支选择,首先检查是否支持动态色彩
  • dynamicDarkColorSchemedynamicLightColorScheme 是MD3提供的动态色彩生成函数,能从设备壁纸中提取颜色
  • darkColorSchemelightColorScheme 定义静态色彩方案,作为动态色彩的备选

这种分层的主题实现方式非常灵活,既支持了最新的动态色彩特性,又兼容了旧版本系统。通过将主题逻辑封装在一个函数中,可以在应用的任何地方方便地使用统一的主题样式。

最后一步,就是把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 中管理这些主题状态,这样整个应用都能方便地访问和修改它们。

技术实现要点

  • 使用 remembermutableStateOf 创建可持久化、可观察的状态变量
  • 状态变量需要在应用的顶层管理,以便在整个应用范围内共享
  • 主题状态通过参数传递给MD3主题函数,实现主题的动态切换

关键状态变量

  1. isDarkTheme: 控制应用是亮色主题还是暗色主题
  2. 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中实现主题切换时,状态管理是核心。使用 remembermutableStateOf 创建的状态变量,能够在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 = "分享"
        )
    }
}

技术实现细节

  1. 默认尺寸FAB不需要额外设置size,系统会自动使用标准的56dp大小
  2. 通过Modifier.size()可以精确控制FAB的尺寸
  3. 使用shape参数可以自定义FAB的形状,MD3建议使用圆角矩形,圆角半径根据尺寸调整
  4. 迷你尺寸FAB通常用于次要操作,如在列表中编辑单个项目
  5. 扩展尺寸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 // 图标颜色使用与第三色匹配的文本色
        )
    }
}

实现效果如下

在这里插入图片描述
技术实现细节

  1. containerColor参数用于设置FAB的背景颜色
  2. 为了确保图标与背景色的对比度符合可访问性标准,应使用与背景色对应的文本色(如onSecondaryonTertiary
  3. 自定义颜色的FAB适合用于强调特定类型的操作,如"收藏"、"提醒"等
  4. 在动态色彩模式下,自定义颜色的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设计规范,使用主题的 primaryContaineronPrimaryContainer 确保良好的对比度和视觉效果

实现效果如下

在这里插入图片描述

在实现这类交互式卡片时,状态管理是核心。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 底部导航的优势与应用场景

底部导航栏是移动应用中最常用的导航模式之一,具有以下优势:

  1. 易于访问:底部导航栏位于屏幕底部,用户可以轻松用拇指触及,符合人体工程学设计
  2. 清晰可见:导航项始终可见,用户可以随时了解当前位置和切换到其他页面
  3. 减少认知负担:有限的导航项数量(通常3-5个)可以帮助用户快速理解应用的主要功能结构
  4. 一致的用户体验:底部导航是移动应用的常见模式,用户已经熟悉这种导航方式

底部导航栏适用于以下应用场景:

  • 具有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 2Material Design 3
色彩系统固定主色、辅助色,无动态色彩支持动态色彩,新增中性色层级
组件样式按钮、卡片等组件样式单一组件样式更丰富,支持更多自定义属性
排版字体层级较少,无动态字体适配完善的字体层级,支持动态字体大小
Compose 支持支持有限深度集成,专属组件库

五、实践建议与注意事项

  • 优先使用 Compose:MD3 对 Compose 的支持更完善,开发效率更高,推荐新项目或迁移项目使用 Compose 实现 MD3 设计。

  • 兼容性处理:动态色彩仅支持 Android 12+,需为低版本设计默认色彩方案;组件样式需适配不同屏幕尺寸和分辨率。

  • 主题一致性:整个应用应统一使用 MD3 主题,避免混合 MD2 和 MD3 组件,确保视觉风格一致。

  • 可访问性优化:利用 MD3 的动态字体、高对比度色彩等特性,提升应用的可访问性,让应用对视力障碍用户更友好。

  • 版本更新:MD3 库仍在持续迭代,建议关注官方文档,及时更新依赖版本,获取最新特性和bug修复。

六、总结

本文详细介绍了 Material Design 3 的核心特性和实现方法,包括动态色彩系统、主题定制、组件实现和导航架构等方面。通过结合 Jetpack Compose,我们可以高效地实现 MD3 的设计理念,创建出视觉吸引力强、交互友好的现代 Android 应用。

在实际开发中,建议根据项目需求选择合适的组件和功能,并结合官方文档和设计规范进行开发,以确保应用的质量和用户体验。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值