Appearance
深色模式适配深度指南
字数统计:约 10000 字
难度等级:⭐⭐⭐⭐⭐
面试重要度:⭐⭐⭐⭐⭐
目录
1. 深色模式概述
1.1 什么是深色模式?
深色模式(Dark Mode)是一种 UI 设计模式,使用深色背景和浅色文字,主要优势包括:
深色模式的优势:
1. 降低夜间使用时的眼睛疲劳
2. 在 OLED 屏幕上更省电
3. 提升视觉舒适度和专注度
4. 符合现代 Material Design 设计趋势
5. 满足用户个性化需求
深色模式的挑战:
1. 需要维护两套颜色资源
2. 图片和图标的适配
3. 阴影和效果的调整
4. 可读性和对比度的保证
5. 测试工作量增加1.2 深色模式的发展历史
深色模式的发展历程:
- 2014 年:Android 5.0 Lollipop 初步支持
- 2017 年:Android 8.0 Oreo 增强支持
- 2018 年:iOS 13 全面支持深色模式
- 2020 年:Android 10 完善深色模式 API
- 2021 年:Android 12 Material You 动态取色
- 2023 年:Android 14 更精细的控制
Android 深色模式 API 演进:
- API 23: AppCompatDelegate 支持
- API 27: Configuration.UI_MODE_NIGHT_MASK
- API 29: UiModeManager.NIGHT_MODE
- API 31: Material Design 3 动态取色1.3 深色模式的实现方式
kotlin
// 方式 1: 使用 DayNight 主题(推荐)
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- 自动根据系统设置切换 -->
</style>
// 方式 2: 代码控制夜间模式
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
// 方式 3: 使用资源限定符
// res/values/colors.xml (浅色)
// res/values-night/colors.xml (深色)
// 方式 4: 使用 ContextThemeWrapper 动态切换
val themedContext = ContextThemeWrapper(context, R.style.AppTheme_Dark)1.4 深色模式的检测机制
kotlin
class DarkModeDetector {
// 方法 1: 通过 Configuration 检测
fun isDarkModeViaConfiguration(context: Context): Boolean {
val config = context.resources.configuration
val uiMode = config.uiMode
return (uiMode and Configuration.UI_MODE_NIGHT_MASK) ==
Configuration.UI_MODE_NIGHT_YES
}
// 方法 2: 通过 UiModeManager 检测(API 29+)
fun isDarkModeViaUiModeManager(context: Context): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val uiModeManager = context.getSystemService(UI_MODE_SERVICE) as UiModeManager
return uiModeManager.nightMode == UiModeManager.MODE_NIGHT_YES
}
return isDarkModeViaConfiguration(context)
}
// 方法 3: 通过 Theme 属性检测
fun isDarkModeViaTheme(context: Context): Boolean {
val attrs = intArrayOf(android.R.attr.isMonochrome)
val ta = context.theme.obtainStyledAttributes(attrs)
val isDark = ta.getBoolean(0, false)
ta.recycle()
return isDark
}
// 监听深色模式变化
fun observeDarkModeChanges(context: Context, callback: (Boolean) -> Unit) {
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Configuration.CONFIGURATION_CHANGED) {
val isDark = isDarkModeViaConfiguration(context)
callback(isDark)
}
}
}
val filter = IntentFilter(Configuration.CONFIGURATION_CHANGED)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(receiver, filter, RECEIVER_NOT_EXPORTED)
} else {
context.registerReceiver(receiver, filter)
}
}
}2. styles.xml 配置详解
2.1 styles.xml 文件结构
xml
<!-- res/values/styles.xml -->
<resources>
<!-- 应用主题 -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- 基础属性 -->
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryVariant">@color/primary_variant</item>
<item name="colorOnPrimary">@color/on_primary</item>
<!-- 强调色 -->
<item name="colorSecondary">@color/secondary</item>
<item name="colorSecondaryVariant">@color/secondary_variant</item>
<item name="colorOnSecondary">@color/on_secondary</item>
<!-- 背景色 -->
<item name="android:colorBackground">@color/background</item>
<item name="colorSurface">@color/surface</item>
<item name="colorOnSurface">@color/on_surface</item>
<!-- 状态色 -->
<item name="colorError">@color/error</item>
<item name="colorOnError">@color/on_error</item>
<!-- 组件样式 -->
<item name="buttonStyle">@style/AppButton</item>
<item name="textInputStyle">@style/AppTextInput</item>
<item name="cardViewStyle">@style/AppCardView</item>
<!-- 文字样式 -->
<item name="textAppearanceHeadline1">@style/TextAppearance.App.Headline</item>
<item name="textAppearanceBody1">@style/TextAppearance.App.Body</item>
</style>
<!-- 自定义按钮样式 -->
<style name="AppButton" parent="Widget.MaterialComponents.Button">
<item name="backgroundTint">@color/primary</item>
<item name="cornerRadius">8dp</item>
<item name="android:textColor">@color/on_primary</item>
<item name="android:textAllCaps">false</item>
</style>
<!-- 自定义卡片样式 -->
<style name="AppCardView" parent="Widget.MaterialComponents.CardView">
<item name="cardBackgroundColor">@color/surface</item>
<item name="cardCornerRadius">12dp</item>
<item name="cardElevation">4dp</item>
</style>
<!-- 自定义输入框样式 -->
<style name="AppTextInput" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<item name="boxStrokeColor">@color/primary</item>
<item name="hintTextColor">@color/primary</item>
</style>
<!-- 文字样式 -->
<style name="TextAppearance.App.Headline" parent="TextAppearance.MaterialComponents.Headline6">
<item name="android:textSize">24sp</item>
<item name="android:textColor">@color/on_surface</item>
<item name="android:textStyle">bold</item>
</style>
<style name="TextAppearance.App.Body" parent="TextAppearance.MaterialComponents.Body1">
<item name="android:textSize">16sp</item>
<item name="android:textColor">@color/on_surface</item>
</style>
</resources>2.2 主题属性详解
xml
<!-- 颜色属性详解 -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- 主色 - 品牌色 -->
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primary_dark</item>
<item name="colorPrimaryVariant">@color/primary_variant</item>
<item name="colorOnPrimary">@color/on_primary</item>
<!-- 强调色 - 次要品牌色 -->
<item name="colorAccent">@color/accent</item>
<item name="colorSecondary">@color/secondary</item>
<item name="colorSecondaryVariant">@color/secondary_variant</item>
<item name="colorOnSecondary">@color/on_secondary</item>
<!-- 背景色 -->
<item name="android:colorBackground">@color/background</item>
<item name="colorBackgroundFloating">@color/background_floating</item>
<!-- 表面色 - Material Design -->
<item name="colorSurface">@color/surface</item>
<item name="colorSurfaceVariant">@color/surface_variant</item>
<item name="colorOnSurface">@color/on_surface</item>
<item name="colorOnSurfaceVariant">@color/on_surface_variant</item>
<!-- 错误色 -->
<item name="colorError">@color/error</item>
<item name="colorOnError">@color/on_error</item>
<!-- 状态栏颜色 -->
<item name="android:statusBarColor">@color/status_bar</item>
<!-- 导航栏颜色 -->
<item name="android:navigationBarColor">@color/navigation_bar</item>
</style>2.3 组件样式配置
xml
<!-- 按钮样式 -->
<style name="AppButton" parent="Widget.MaterialComponents.Button">
<item name="backgroundTint">@color/primary</item>
<item name="cornerRadius">8dp</item>
<item name="android:textColor">@color/on_primary</item>
<item name="android:textAllCaps">false</item>
<item name="android:paddingHorizontal">24dp</item>
<item name="android:paddingVertical">12dp</item>
<item name="rippleColor">@color/ripple</item>
</style>
<style name="AppButton.Outlined" parent="Widget.MaterialComponents.Button.OutlinedButton">
<item name="strokeColor">@color/primary</item>
<item name="android:textColor">@color/primary</item>
<item name="cornerRadius">8dp</item>
</style>
<style name="AppButton.Text" parent="Widget.MaterialComponents.Button.TextButton">
<item name="android:textColor">@color/primary</item>
<item name="iconTint">@color/primary</item>
</style>
<style name="AppButton.Elevated" parent="Widget.MaterialComponents.Button">
<item name="backgroundTint">@color/primary</item>
<item name="elevation">4dp</item>
<item name="cornerRadius">4dp</item>
</style>
<!-- CardView 样式 -->
<style name="AppCardView" parent="Widget.MaterialComponents.CardView">
<item name="cardBackgroundColor">@color/surface</item>
<item name="cardCornerRadius">12dp</item>
<item name="cardElevation">4dp</item>
<item name="cardPreventCornerOverlap">false</item>
<item name="cardContentPadding">16dp</item>
</style>
<style name="AppCardView.Elevated" parent="Widget.MaterialComponents.ElevatedCard">
<item name="cardElevation">8dp</item>
<item name="cardCornerRadius">12dp</item>
</style>
<style name="AppCardView.Outlined" parent="Widget.MaterialComponents.OutlinedCard">
<item name="strokeWidth">1dp</item>
<item name="strokeColor">@color/outline</item>
<item name="cardCornerRadius">12dp</item>
</style>
<!-- FloatingActionButton 样式 -->
<style name="AppFAB" parent="Widget.MaterialComponents.FloatingActionButton">
<item name="backgroundTint">@color/primary</item>
<item name="tint">@color/on_primary</item>
<item name="elevation">6dp</item>
<item name="borderWidth">0dp</item>
</style>
<style name="AppFAB.Secondary" parent="Widget.MaterialComponents.FloatingActionButton">
<item name="backgroundTint">@color/secondary</item>
<item name="tint">@color/on_secondary</item>
</style>
<style name="AppFAB.Small" parent="Widget.MaterialComponents.FloatingActionButton">
<item name="fabSize">normal</item>
<item name="backgroundTint">@color/primary</item>
<item name="tint">@color/on_primary</item>
</style>
<style name="AppFAB.Extended" parent="Widget.MaterialComponents.ExtendedFloatingActionButton">
<item name="backgroundTint">@color/primary</item>
<item name="iconTint">@color/on_primary</item>
<item name="iconGravity">textStart</item>
</style>
<!-- TextInputLayout 样式 -->
<style name="AppTextInput" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<item name="boxStrokeColor">@color/primary</item>
<item name="hintTextColor">@color/primary</item>
<item name="boxCornerRadiusTopStart">8dp</item>
<item name="boxCornerRadiusTopEnd">8dp</item>
<item name="boxCornerRadiusBottomStart">8dp</item>
<item name="boxCornerRadiusBottomEnd">8dp</item>
<item name="errorTextColor">@color/error</item>
</style>
<style name="AppTextInput.Filled" parent="Widget.MaterialComponents.TextInputLayout.FilledBox">
<item name="hintTextColor">@color/primary</item>
<item name="startIconTint">@color/primary</item>
<item name="endIconTint">@color/primary</item>
</style>
<!-- Snackbar 样式 -->
<style name="AppSnackbar" parent="Widget.MaterialComponents.Snackbar">
<item name="android:background">@drawable/snackbar_background</item>
<item name="actionTextColor">@color/on_surface</item>
</style>
<style name="AppSnackbar.Text" parent="Widget.MaterialComponents.Snackbar.TextView">
<item name="android:textColor">@color/on_surface</item>
</style>
<!-- BottomNavigationView 样式 -->
<style name="AppBottomNavigation" parent="Widget.MaterialComponents.BottomNavigationView">
<item name="itemIconTint">@color/bottom_nav_icon</item>
<item name="itemTextColor">@color/bottom_nav_text</item>
<item name="backgroundTint">@color/surface</item>
<item name="labelVisibilityMode">labeled</item>
</style>
<!-- TabLayout 样式 -->
<style name="AppTabLayout" parent="Widget.MaterialComponents.TabLayout">
<item name="tabIndicatorColor">@color/primary</item>
<item name="tabSelectedTextColor">@color/primary</item>
<item name="tabTextColor">@color/secondary_text</item>
<item name="tabIndicatorHeight">2dp</item>
</style>
<!-- ProgressBar 样式 -->
<style name="AppCircularProgress" parent="Widget.MaterialComponents.ProgressIndicator">
<item name="indicatorColor">@color/primary</item>
<item name="trackColor">@color/track</item>
</style>
<style name="AppLinearProgress" parent="Widget.MaterialComponents.LinearProgressIndicator">
<item name="indicatorColor">@color/primary</item>
<item name="trackColor">@color/track</item>
</style>
<!-- Chip 样式 -->
<style name="AppChip" parent="Widget.MaterialComponents.Chip.Choice">
<item name="chipBackgroundColor">@color/chip_background</item>
<item name="chipStrokeColor">@color/chip_stroke</item>
<item name="chipIconTint">@color/chip_icon</item>
<item name="android:textColor">@color/chip_text</item>
</style>
<style name="AppChip.Filter" parent="Widget.MaterialComponents.Chip.Filter">
<item name="chipBackgroundColor">@color/chip_filter_background</item>
<item name="chipStrokeColor">@color/chip_filter_stroke</item>
</style>
<!-- CheckBox 样式 -->
<style name="AppCheckBox" parent="Widget.MaterialComponents.Checkbox">
<item name="buttonTint">@color/primary</item>
</style>
<!-- RadioButton 样式 -->
<style name="AppRadioButton" parent="Widget.MaterialComponents.RadioButton">
<item name="buttonTint">@color/primary</item>
</style>
<!-- Switch 样式 -->
<style name="AppSwitch" parent="Widget.MaterialComponents.Switch">
<item name="thumbTint">@color/switch_thumb</item>
<item name="trackTint">@color/switch_track</item>
</style>2.4 对话框样式配置
xml
<!-- 对话框样式 -->
<style name="AppDialog" parent="Theme.MaterialComponents.DayNight.Dialog">
<item name="android:windowBackground">@drawable/dialog_background</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowAnimationStyle">@style/AppDialogAnimation</item>
</style>
<style name="AppDialog.Alert" parent="Theme.MaterialComponents.DayNight.Dialog.Alert">
<item name="android:windowBackground">@drawable/dialog_alert_background</item>
<item name="buttonBarPositiveButtonStyle">@style/AppDialogPositiveButton</item>
<item name="buttonBarNegativeButtonStyle">@style/AppDialogNegativeButton</item>
</style>
<style name="AppDialog.Fullscreen" parent="Theme.MaterialComponents.DayNight.Dialog.Fullscreen">
<item name="android:windowContentOverlay">@null</item>
<item name="android:backgroundDimEnabled">false</item>
</style>
<!-- BottomSheet 样式 -->
<style name="AppBottomSheet" parent="Theme.MaterialComponents.DayNight.BottomSheetDialog">
<item name="bottomSheetStyle">@style/AppBottomSheetStyle</item>
</style>
<style name="AppBottomSheetStyle" parent="Widget.MaterialComponents.BottomSheet.Modal">
<item name="backgroundTint">@color/bottom_sheet_background</item>
<item name="shapeAppearanceOverlay">@style/AppBottomSheetShape</item>
</style>
<style name="AppBottomSheetShape">
<item name="cornerFamily">rounded</item>
<item name="cornerSizeTopStart">16dp</item>
<item name="cornerSizeTopEnd">16dp</item>
<item name="cornerSizeBottomStart">0dp</item>
<item name="cornerSizeBottomEnd">0dp</item>
</style>
<!-- 对话框按钮样式 -->
<style name="AppDialogPositiveButton" parent="Widget.MaterialComponents.Button.TextButton">
<item name="android:textColor">@color/primary</item>
<item name="android:textAllCaps">false</item>
</style>
<style name="AppDialogNegativeButton" parent="Widget.MaterialComponents.Button.TextButton">
<item name="android:textColor">@color/secondary_text</item>
<item name="android:textAllCaps">false</item>
</style>
<!-- 对话框动画 -->
<style name="AppDialogAnimation">
<item name="android:windowEnterAnimation">@anim/dialog_enter</item>
<item name="android:windowExitAnimation">@anim/dialog_exit</item>
</style>3. themes.xml 配置详解
3.1 浅色模式主题配置
xml
<!-- res/values/themes.xml -->
<resources>
<!-- 基础浅色主题 -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- 主色 - 蓝色系 -->
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryVariant">@color/primary_variant</item>
<item name="colorOnPrimary">@color/on_primary</item>
<item name="colorPrimaryContainer">@color/primary_container</item>
<item name="colorOnPrimaryContainer">@color/on_primary_container</item>
<!-- 强调色 - 青色系 -->
<item name="colorSecondary">@color/secondary</item>
<item name="colorSecondaryVariant">@color/secondary_variant</item>
<item name="colorOnSecondary">@color/on_secondary</item>
<item name="colorSecondaryContainer">@color/secondary_container</item>
<item name="colorOnSecondaryContainer">@color/on_secondary_container</item>
<!-- 背景色 -->
<item name="android:colorBackground">@color/background</item>
<item name="colorBackgroundFloating">@color/background_floating</item>
<item name="colorOnBackground">@color/on_background</item>
<!-- 表面色 -->
<item name="colorSurface">@color/surface</item>
<item name="colorSurfaceVariant">@color/surface_variant</item>
<item name="colorOnSurface">@color/on_surface</item>
<item name="colorOnSurfaceVariant">@color/on_surface_variant</item>
<!-- 错误色 -->
<item name="colorError">@color/error</item>
<item name="colorOnError">@color/on_error</item>
<item name="colorErrorContainer">@color/error_container</item>
<item name="colorOnErrorContainer">@color/on_error_container</item>
<!-- 状态栏和导航栏 -->
<item name="android:statusBarColor">@color/status_bar</item>
<item name="android:navigationBarColor">@color/navigation_bar</item>
</style>
<!-- 无 ActionBar 主题 -->
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
<!-- 全屏主题 -->
<style name="AppTheme.FullScreen" parent="AppTheme">
<item name="android:windowFullscreen">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
<!-- 透明主题 -->
<style name="AppTheme.Transparent" parent="AppTheme">
<item name="android:windowIsTranslucent">true</item>
<item name="android:background">@android:color/transparent</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowContentOverlay">@null</item>
</style>
<!-- 启动屏主题 -->
<style name="AppTheme.Launcher" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:statusBarColor">@color/primary</item>
</style>
<!-- Material Design 3 浅色主题 -->
<style name="AppTheme.M3" parent="Theme.Material3.DayNight.NoActionBar">
<item name="colorPrimary">@color/md3_primary</item>
<item name="colorOnPrimary">@color/md3_on_primary</item>
<item name="colorPrimaryContainer">@color/md3_primary_container</item>
<item name="colorOnPrimaryContainer">@color/md3_on_primary_container</item>
<item name="colorSecondary">@color/md3_secondary</item>
<item name="colorOnSecondary">@color/md3_on_secondary</item>
<item name="colorSecondaryContainer">@color/md3_secondary_container</item>
<item name="colorOnSecondaryContainer">@color/md3_on_secondary_container</item>
<item name="colorTertiary">@color/md3_tertiary</item>
<item name="colorOnTertiary">@color/md3_on_tertiary</item>
<item name="colorTertiaryContainer">@color/md3_tertiary_container</item>
<item name="colorOnTertiaryContainer">@color/md3_on_tertiary_container</item>
<item name="colorError">@color/md3_error</item>
<item name="colorOnError">@color/md3_on_error</item>
<item name="colorErrorContainer">@color/md3_error_container</item>
<item name="colorOnErrorContainer">@color/md3_on_error_container</item>
<item name="android:colorBackground">@color/md3_background</item>
<item name="colorOnBackground">@color/md3_on_background</item>
<item name="colorSurface">@color/md3_surface</item>
<item name="colorOnSurface">@color/md3_on_surface</item>
<item name="colorSurfaceVariant">@color/md3_surface_variant</item>
<item name="colorOnSurfaceVariant">@color/md3_on_surface_variant</item>
<item name="colorOutline">@color/md3_outline</item>
<item name="colorOutlineVariant">@color/md3_outline_variant</item>
</style>
</resources>3.2 深色模式主题配置
xml
<!-- res/values-night/themes.xml -->
<resources>
<!-- 基础深色主题 -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- 主色 - 深色模式下的浅色变体 -->
<item name="colorPrimary">@color/primary_dark</item>
<item name="colorPrimaryVariant">@color/primary_variant_dark</item>
<item name="colorOnPrimary">@color/on_primary_dark</item>
<item name="colorPrimaryContainer">@color/primary_container_dark</item>
<item name="colorOnPrimaryContainer">@color/on_primary_container_dark</item>
<!-- 强调色 -->
<item name="colorSecondary">@color/secondary_dark</item>
<item name="colorSecondaryVariant">@color/secondary_variant_dark</item>
<item name="colorOnSecondary">@color/on_secondary_dark</item>
<item name="colorSecondaryContainer">@color/secondary_container_dark</item>
<item name="colorOnSecondaryContainer">@color/on_secondary_container_dark</item>
<!-- 背景色 - 深色 -->
<item name="android:colorBackground">@color/background_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_dark</item>
<item name="colorOnBackground">@color/on_background_dark</item>
<!-- 表面色 - 深色 -->
<item name="colorSurface">@color/surface_dark</item>
<item name="colorSurfaceVariant">@color/surface_variant_dark</item>
<item name="colorOnSurface">@color/on_surface_dark</item>
<item name="colorOnSurfaceVariant">@color/on_surface_variant_dark</item>
<!-- 错误色 -->
<item name="colorError">@color/error_dark</item>
<item name="colorOnError">@color/on_error_dark</item>
<item name="colorErrorContainer">@color/error_container_dark</item>
<item name="colorOnErrorContainer">@color/on_error_container_dark</item>
<!-- 状态栏和导航栏 -->
<item name="android:statusBarColor">@color/status_bar_dark</item>
<item name="android:navigationBarColor">@color/navigation_bar_dark</item>
</style>
<!-- Material Design 3 深色主题 -->
<style name="AppTheme.M3" parent="Theme.Material3.DayNight.NoActionBar">
<item name="colorPrimary">@color/md3_primary_dark</item>
<item name="colorOnPrimary">@color/md3_on_primary_dark</item>
<item name="colorPrimaryContainer">@color/md3_primary_container_dark</item>
<item name="colorOnPrimaryContainer">@color/md3_on_primary_container_dark</item>
<item name="colorSecondary">@color/md3_secondary_dark</item>
<item name="colorOnSecondary">@color/md3_on_secondary_dark</item>
<item name="colorSecondaryContainer">@color/md3_secondary_container_dark</item>
<item name="colorOnSecondaryContainer">@color/md3_on_secondary_container_dark</item>
<item name="colorTertiary">@color/md3_tertiary_dark</item>
<item name="colorOnTertiary">@color/md3_on_tertiary_dark</item>
<item name="colorTertiaryContainer">@color/md3_tertiary_container_dark</item>
<item name="colorOnTertiaryContainer">@color/md3_on_tertiary_container_dark</item>
<item name="colorError">@color/md3_error_dark</item>
<item name="colorOnError">@color/md3_on_error_dark</item>
<item name="colorErrorContainer">@color/md3_error_container_dark</item>
<item name="colorOnErrorContainer">@color/md3_on_error_container_dark</item>
<item name="android:colorBackground">@color/md3_background_dark</item>
<item name="colorOnBackground">@color/md3_on_background_dark</item>
<item name="colorSurface">@color/md3_surface_dark</item>
<item name="colorOnSurface">@color/md3_on_surface_dark</item>
<item name="colorSurfaceVariant">@color/md3_surface_variant_dark</item>
<item name="colorOnSurfaceVariant">@color/md3_on_surface_variant_dark</item>
<item name="colorOutline">@color/md3_outline_dark</item>
<item name="colorOutlineVariant">@color/md3_outline_variant_dark</item>
</style>
</resources>3.3 主题变体配置
xml
<!-- 不同场景的主题变体 -->
<resources>
<!-- 登录页主题 -->
<style name="AppTheme.Login" parent="AppTheme">
<item name="android:windowBackground">@drawable/login_background</item>
<item name="colorPrimary">@color/login_primary</item>
</style>
<!-- 详情页主题 -->
<style name="AppTheme.Detail" parent="AppTheme">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:statusBarColor">@color/detail_status_bar</item>
</style>
<!-- 视频播放主题 -->
<style name="AppTheme.VideoPlayer" parent="AppTheme">
<item name="android:windowFullscreen">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowContentOverlay">@null</item>
</style>
<!-- 编辑页主题 -->
<style name="AppTheme.Editor" parent="AppTheme">
<item name="colorPrimary">@color/editor_primary</item>
<item name="android:windowSoftInputMode">adjustResize</item>
</style>
<!-- 设置页主题 -->
<style name="AppTheme.Settings" parent="AppTheme">
<item name="colorPrimary">@color/settings_primary</item>
<item name="android:windowBackground">@color/settings_background</item>
</style>
</resources>3.4 主题继承结构
xml
<!-- 主题继承结构示例 -->
<resources>
<!-- 第一层:基础主题 -->
<style name="Base.AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="colorPrimary">@color/primary</item>
<item name="colorSecondary">@color/secondary</item>
</style>
<!-- 第二层:带 ActionBar 的主题 -->
<style name="AppTheme.ActionBar" parent="Base.AppTheme">
<item name="windowActionBar">true</item>
<item name="windowNoTitle">false</item>
</style>
<!-- 第二层:不带 ActionBar 的主题 -->
<style name="AppTheme.NoActionBar" parent="Base.AppTheme">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
<!-- 第三层:全屏主题 -->
<style name="AppTheme.FullScreen" parent="AppTheme.NoActionBar">
<item name="android:windowFullscreen">true</item>
</style>
<!-- 第三层:透明主题 -->
<style name="AppTheme.Transparent" parent="AppTheme.NoActionBar">
<item name="android:windowIsTranslucent">true</item>
<item name="android:background">@android:color/transparent</item>
</style>
<!-- 第四层:具体场景主题 -->
<style name="AppTheme.Splash" parent="AppTheme.FullScreen">
<item name="android:windowBackground">@drawable/splash_background</item>
</style>
<style name="AppTheme.Login" parent="AppTheme.NoActionBar">
<item name="android:windowBackground">@drawable/login_background</item>
</style>
</resources>4. 资源限定符完整指南
4.1 资源限定符类型
资源限定符类型:
1. 配置限定符 (Configuration Qualifiers)
- -night (夜间模式)
- -day (日间模式)
- -vXX (API 版本)
- -wXXdp (最小宽度)
- -hXXdp (最小高度)
- -land (横屏)
- -port (竖屏)
2. 语言限定符
- -zh (中文)
- -en (英文)
3. 密度限定符
- -mdpi (中密度)
- -hdpi (高密度)
- -xhdpi (超高密度)
- -xxhdpi (超高超高密度)
- -xxxhdpi (超超高超高密度)
4. 组合限定符
- -zh-rCN (中国大陆中文)
- -en-rUS (美国英文)4.2 资源目录结构
res/
├── values/
│ ├── colors.xml # 默认颜色 (浅色)
│ ├── themes.xml # 默认主题 (浅色)
│ ├── strings.xml
│ ├── dimens.xml
│ └── styles.xml
├── values-night/
│ ├── colors.xml # 深色模式颜色
│ ├── themes.xml # 深色模式主题
│ └── styles.xml
├── values-v21/
│ ├── themes.xml # API 21+ 主题
├── values-v21-night/
│ ├── themes.xml # API 21+ 深色主题
├── values-v31/
│ ├── themes.xml # API 31+ 主题 (Material 3)
├── values-v31-night/
│ ├── themes.xml # API 31+ 深色主题
├── drawable/
│ ├── ic_logo.xml # 默认图标
│ ├── bg_card.xml # 默认卡片背景
├── drawable-night/
│ ├── ic_logo.xml # 深色模式图标
│ ├── bg_card.xml # 深色模式卡片背景
├── layout/
│ └── activity_main.xml
├── layout-night/
│ └── activity_main.xml # 深色模式布局
├── mipmap-anydpi-v26/
│ └── ic_launcher.xml
├── mipmap-anydpi-v26-night/
│ └── ic_launcher.xml # 深色模式启动图标
├── color/
│ ├── button_color.xml # 颜色选择器
├── color-night/
│ └── button_color.xml # 深色模式颜色选择器
└── animator/
├── fade_in.xml
└── animator-night/
└── fade_in.xml4.3 颜色资源限定符
xml
<!-- res/values/colors.xml (浅色模式) -->
<resources>
<!-- 主色 -->
<color name="primary">#1976D2</color>
<color name="primary_variant">#1565C0</color>
<color name="on_primary">#FFFFFF</color>
<!-- 强调色 -->
<color name="secondary">#00897B</color>
<color name="secondary_variant">#00796B</color>
<color name="on_secondary">#FFFFFF</color>
<!-- 背景色 -->
<color name="background">#FAFAFA</color>
<color name="background_floating">#FFFFFF</color>
<color name="on_background">#000000</color>
<!-- 表面色 -->
<color name="surface">#FFFFFF</color>
<color name="surface_variant">#E0E0E0</color>
<color name="on_surface">#000000</color>
<color name="on_surface_variant">#424242</color>
<!-- 错误色 -->
<color name="error">#B00020</color>
<color name="on_error">#FFFFFF</color>
<!-- 文本颜色 -->
<color name="primary_text">#000000</color>
<color name="secondary_text">#66000000</color>
<color name="hint_text">#99000000</color>
<color name="disabled_text">#66000000</color>
<!-- 边框和分割线 -->
<color name="divider">#E0E0E0</color>
<color name="outline">#BDBDBD</color>
<!-- 状态栏和导航栏 -->
<color name="status_bar">#FFFFFF</color>
<color name="navigation_bar">#FFFFFF</color>
<!-- Ripple 效果 -->
<color name="ripple">#33000000</color>
</resources>
<!-- res/values-night/colors.xml (深色模式) -->
<resources>
<!-- 主色 - 浅色变体 -->
<color name="primary">#90CAF9</color>
<color name="primary_variant">#64B5F6</color>
<color name="on_primary">#000000</color>
<!-- 强调色 - 浅色变体 -->
<color name="secondary">#80CBC4</color>
<color name="secondary_variant">#4DB6AC</color>
<color name="on_secondary">#000000</color>
<!-- 背景色 - 深色 -->
<color name="background">#121212</color>
<color name="background_floating">#1E1E1E</color>
<color name="on_background">#FFFFFF</color>
<!-- 表面色 - 深色 -->
<color name="surface">#1E1E1E</color>
<color name="surface_variant">#2C2C2C</color>
<color name="on_surface">#FFFFFF</color>
<color name="on_surface_variant">#B0B0B0</color>
<!-- 错误色 -->
<color name="error">#CF6679</color>
<color name="on_error">#690005</color>
<!-- 文本颜色 -->
<color name="primary_text">#FFFFFF</color>
<color name="secondary_text">#B3FFFFFF</color>
<color name="hint_text">#80FFFFFF</color>
<color name="disabled_text">#4DFFFFFF</color>
<!-- 边框和分割线 -->
<color name="divider">#33FFFFFF</color>
<color name="outline">#4DFFFFFF</color>
<!-- 状态栏和导航栏 -->
<color name="status_bar">#121212</color>
<color name="navigation_bar">#121212</color>
<!-- Ripple 效果 -->
<color name="ripple">#33FFFFFF</color>
</resources>4.4 Drawable 资源限定符
xml
<!-- res/drawable/ic_logo.xml (浅色模式) -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#1976D2"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2z"/>
<path
android:fillColor="#FFFFFF"
android:pathData="M12,17l-5,-5 1.41,-1.41L12,14.17l4.59,-4.58L18,11z"/>
</vector>
<!-- res/drawable-night/ic_logo.xml (深色模式) -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#90CAF9"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2z"/>
<path
android:fillColor="#000000"
android:pathData="M12,17l-5,-5 1.41,-1.41L12,14.17l4.59,-4.58L18,11z"/>
</vector>4.5 Color State List 限定符
xml
<!-- res/color/button_color.xml (浅色模式) -->
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="#CCCCCC"/>
<item android:state_pressed="true" android:color="#1565C0"/>
<item android:color="#1976D2"/>
</selector>
<!-- res/color-night/button_color.xml (深色模式) -->
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="#424242"/>
<item android:state_pressed="true" android:color="#64B5F6"/>
<item android:color="#90CAF9"/>
</selector>4.6 组合限定符
xml
<!-- 组合限定符示例 -->
res/
├── values/
│ └── colors.xml # 默认
├── values-night/
│ └── colors.xml # 深色
├── values-v21/
│ └── themes.xml # API 21+
├── values-v21-night/
│ └── themes.xml # API 21+ 深色
├── values-v31/
│ └── themes.xml # API 31+
├── values-v31-night/
│ └── themes.xml # API 31+ 深色
├── values-zh-rCN/
│ └── strings.xml # 简体中文
├── values-zh-rCN-night/
│ └── strings.xml # 简体中文 深色
├── values-sw360dp/
│ └── dimens.xml # 最小宽度 360dp
├── values-sw600dp/
│ └── dimens.xml # 平板
└── values-sw600dp-night/
└── dimens.xml # 平板 深色4.7 资源限定符优先级
资源限定符的优先级(从高到低):
1. 语言 + 区域 + 其他组合限定符
2. 触摸屏幕类型
3. 密度
4. 屏幕尺寸
5. 屏幕长宽比
6. 夜间模式
7. API 版本
8. 屏幕方向
9. 键盘类型
10. 默认资源
示例:
res/values-night/colors.xml 优先级高于 res/values/colors.xml
res/values-v31-night/colors.xml 优先级最高5. 组件适配最佳实践
5.1 TextView 适配
xml
<!-- 浅色模式 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World"
android:textColor="?attr/colorOnSurface"
android:textAppearance="?attr/textAppearanceBody1" />
<!-- 使用主题属性自动适配深色模式 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Link Text"
android:textColor="?attr/colorAccent"
android:textStyle="bold" />
<!-- 代码中获取颜色 -->
class TextViewAdapter {
private fun setTextColor(textView: TextView) {
val color = ThemeUtils.getColor(context, R.attr.colorOnSurface)
textView.setTextColor(color)
}
private fun setTextColorSecondary(textView: TextView) {
val color = ThemeUtils.getColor(context, R.attr.colorOnSurfaceVariant)
textView.setTextColor(color)
}
}5.2 Button 适配
xml
<!-- 使用 Material Button 自动适配 -->
<com.google.android.material.button.MaterialButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Submit"
app:backgroundTint="?attr/colorPrimary"
app:iconTint="?attr/colorOnPrimary" />
<!-- Outlined Button -->
<com.google.android.material.button.MaterialButton
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cancel"
app:strokeColor="?attr/colorPrimary"
app:iconTint="?attr/colorPrimary" />
<!-- Text Button -->
<com.google.android.material.button.MaterialButton
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Learn More" />5.3 CardView 适配
xml
<!-- 使用 MaterialCardView 自动适配 -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="?attr/colorSurface"
app:cardElevation="4dp"
app:cardCornerRadius="12dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Card Title"
android:textColor="?attr/colorOnSurface"
android:textAppearance="?attr/textAppearanceHeadline6" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Card Content"
android:textColor="?attr/colorOnSurfaceVariant" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- ElevatedCard -->
<com.google.android.material.card.MaterialCardView
style="@style/Widget.MaterialComponents.ElevatedCard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="8dp">
</com.google.android.material.card.MaterialCardView>
<!-- OutlinedCard -->
<com.google.android.material.card.MaterialCardView
style="@style/Widget.MaterialComponents.OutlinedCard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:strokeWidth="1dp"
app:strokeColor="?attr/colorOutline">
</com.google.android.material.card.MaterialCardView>5.4 EditText/TextInputLayout 适配
xml
<!-- OutlinedBox 风格 -->
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
app:boxStrokeColor="?attr/colorPrimary"
app:hintTextColor="?attr/colorPrimary">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Email"
android:textColor="?attr/colorOnSurface"
android:textColorHint="?attr/colorOnSurfaceVariant" />
</com.google.android.material.textfield.TextInputLayout>
<!-- FilledBox 风格 -->
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
app:hintTextColor="?attr/colorPrimary">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<!-- 带图标 -->
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
app:startIconDrawable="@drawable/ic_email"
app:startIconTint="?attr/colorPrimary"
app:endIconMode="clear_text"
app:endIconTint="?attr/colorOnSurfaceVariant">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Email" />
</com.google.android.material.textfield.TextInputLayout>5.5 FloatingActionButton 适配
xml
<!-- Standard FAB -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_add"
app:backgroundTint="?attr/colorPrimary"
app:tint="?attr/colorOnPrimary"
app:elevation="6dp" />
<!-- Secondary FAB -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_settings"
app:backgroundTint="?attr/colorSecondary"
app:tint="?attr/colorOnSecondary" />
<!-- Extended FAB -->
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add Item"
android:icon="@drawable/ic_add"
app:backgroundTint="?attr/colorPrimary"
app:iconTint="?attr/colorOnPrimary" />5.6 Snackbar 适配
kotlin
// Snackbar 自动适配深色模式
fun showSnackbar(view: View, message: String) {
Snackbar.make(view, message, Snackbar.LENGTH_SHORT)
.setAction("Undo") {
// Action
}
.show()
}
// 自定义 Snackbar 样式
<style name="AppSnackbar" parent="Widget.MaterialComponents.Snackbar">
<item name="backgroundTint">@color/snackbar_background</item>
<item name="actionTextColor">@color/snackbar_action</item>
</style>
<!-- 使用颜色选择器 -->
<!-- res/color/snackbar_background.xml -->
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#323232"/>
</selector>
<!-- res/color-night/snackbar_background.xml -->
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#D32F2F"/>
</selector>5.7 ProgressBar 适配
xml
<!-- Circular Progress -->
<com.google.android.material.progressindicator.CircularProgressIndicator
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:indicatorColor="?attr/colorPrimary"
app:trackColor="?attr/colorSurfaceVariant" />
<!-- Linear Progress -->
<com.google.android.material.progressindicator.LinearProgressIndicator
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:indicatorColor="?attr/colorPrimary"
app:trackColor="?attr/colorSurfaceVariant" />
<!-- Determinate Progress -->
<com.google.android.material.progressindicator.LinearProgressIndicator
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="false"
app:indicatorColor="?attr/colorPrimary"
app:trackColor="?attr/colorSurfaceVariant"
app:progress="50" />5.8 BottomNavigationView 适配
xml
<com.google.android.material.bottomnavigation.BottomNavigationView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:itemIconTint="@color/bottom_nav_icon_color"
app:itemTextColor="@color/bottom_nav_text_color"
app:backgroundTint="?attr/colorSurface"
app:menu="@menu/bottom_nav_menu" />
<!-- res/color/bottom_nav_icon_color.xml -->
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:color="@color/primary"/>
<item android:color="@color/secondary_text"/>
</selector>
<!-- res/color-night/bottom_nav_icon_color.xml -->
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:color="@color/primary_dark"/>
<item android:color="@color/secondary_text_dark"/>
</selector>5.9 TabLayout 适配
xml
<com.google.android.material.tabs.TabLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabIndicatorColor="?attr/colorPrimary"
app:tabSelectedTextColor="?attr/colorPrimary"
app:tabTextColor="?attr/colorOnSurfaceVariant"
app:tabIndicatorHeight="2dp" />5.10 Chip 适配
xml
<!-- Filter Chip -->
<com.google.android.material.chip.Chip
style="@style/Widget.MaterialComponents.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Filter"
app:chipBackgroundColor="@color/chip_filter_background"
app:chipIconTint="?attr/colorPrimary" />
<!-- Choice Chip -->
<com.google.android.material.chip.Chip
style="@style/Widget.MaterialComponents.Chip.Choice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Option"
app:chipBackgroundColor="@color/chip_choice_background" />
<!-- Action Chip -->
<com.google.android.material.chip.Chip
style="@style/Widget.MaterialComponents.Chip.Action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Action"
app:chipIcon="@drawable/ic_phone"
app:chipIconTint="?attr/colorPrimary" />5.11 Dialog 适配
xml
<!-- 对话框背景 -->
<!-- res/drawable/dialog_background.xml -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/surface"/>
<corners android:radius="28dp"/>
</shape>
<!-- res/drawable-night/dialog_background.xml -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/surface_dark"/>
<corners android:radius="28dp"/>
</shape>
<!-- 在代码中创建对话框 -->
class DialogHelper {
fun showAlertDialog(context: Context, title: String, message: String) {
AlertDialog.Builder(context)
.setTitle(title)
.setMessage(message)
.setPositiveButton("OK", null)
.setNegativeButton("Cancel", null)
.show()
}
}5.12 BottomSheet 适配
xml
<!-- BottomSheet 背景 -->
<!-- res/drawable/bottom_sheet_background.xml -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/surface"/>
<corners
android:topLeftRadius="16dp"
android:topRightRadius="16dp"/>
</shape>
<!-- res/drawable-night/bottom_sheet_background.xml -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/surface_dark"/>
<corners
android:topLeftRadius="16dp"
android:topRightRadius="16dp"/>
</shape>
<!-- BottomSheetDialog 样式 -->
<style name="AppBottomSheet" parent="Theme.MaterialComponents.DayNight.BottomSheetDialog">
<item name="bottomSheetStyle">@style/AppBottomSheetStyle</item>
</style>
<style name="AppBottomSheetStyle" parent="Widget.MaterialComponents.BottomSheet.Modal">
<item name="backgroundTint">@drawable/bottom_sheet_background</item>
</style>6. 颜色系统设计
6.1 Material Design 3 颜色系统
xml
<!-- Material Design 3 浅色模式颜色 -->
<!-- res/values/colors.xml -->
<resources>
<!-- 主色系 -->
<color name="md3_primary">#1D60A6</color>
<color name="md3_on_primary">#FFFFFF</color>
<color name="md3_primary_container">#D2E3FF</color>
<color name="md3_on_primary_container">#001D36</color>
<!-- 次要色系 -->
<color name="md3_secondary">#4F6376</color>
<color name="md3_on_secondary">#FFFFFF</color>
<color name="md3_secondary_container">#D2E3FF</color>
<color name="md3_on_secondary_container">#001D36</color>
<!-- 第三色系 -->
<color name="md3_tertiary">#6C5873</color>
<color name="md3_on_tertiary">#FFFFFF</color>
<color name="md3_tertiary_container">#F1DAF9</color>
<color name="md3_on_tertiary_container">#231329</color>
<!-- 错误色 -->
<color name="md3_error">#BA1A1A</color>
<color name="md3_on_error">#FFFFFF</color>
<color name="md3_error_container">#FFDAD6</color>
<color name="md3_on_error_container">#410002</color>
<!-- 背景色 -->
<color name="md3_background">#FEFDF7</color>
<color name="md3_on_background">#1A1C1E</color>
<!-- 表面色 -->
<color name="md3_surface">#FEFDF7</color>
<color name="md3_on_surface">#1A1C1E</color>
<color name="md3_surface_variant">#DFE3EA</color>
<color name="md3_on_surface_variant">#43474E</color>
<!-- 轮廓线 -->
<color name="md3_outline">#747880</color>
<color name="md3_outline_variant">#C4C7D0</color>
</resources>
<!-- Material Design 3 深色模式颜色 -->
<!-- res/values-night/colors.xml -->
<resources>
<!-- 主色系 - 深色模式 -->
<color name="md3_primary">#A6C9FF</color>
<color name="md3_on_primary">#003058</color>
<color name="md3_primary_container">#004581</color>
<color name="md3_on_primary_container">#D2E3FF</color>
<!-- 次要色系 - 深色模式 -->
<color name="md3_secondary">#B7CDE4</color>
<color name="md3_on_secondary">#1F3244</color>
<color name="md3_secondary_container">#364A5E</color>
<color name="md3_on_secondary_container">#D2E3FF</color>
<!-- 第三色系 - 深色模式 -->
<color name="md3_tertiary">#D4BCE4</color>
<color name="md3_on_tertiary">#392841</color>
<color name="md3_tertiary_container">#52405A</color>
<color name="md3_on_tertiary_container">#F1DAF9</color>
<!-- 错误色 - 深色模式 -->
<color name="md3_error">#FFB4AB</color>
<color name="md3_on_error">#690005</color>
<color name="md3_error_container">#93000A</color>
<color name="md3_on_error_container">#FFDAD6</color>
<!-- 背景色 - 深色模式 -->
<color name="md3_background">#191B1D</color>
<color name="md3_on_background">#E1E3E9</color>
<!-- 表面色 - 深色模式 -->
<color name="md3_surface">#191B1D</color>
<color name="md3_on_surface">#E1E3E9</color>
<color name="md3_surface_variant">#43474E</color>
<color name="md3_on_surface_variant">#C4C7D0</color>
<!-- 轮廓线 - 深色模式 -->
<color name="md3_outline">#8C9099</color>
<color name="md3_outline_variant">#43474E</color>
</resources>6.2 颜色对比度检查
kotlin
class ColorContrastChecker {
companion object {
// WCAG 2.1 AA 标准要求
const val AA_LARGE_TEXT_RATIO = 3.0
const val AA_NORMAL_TEXT_RATIO = 4.5
const val AAA_LARGE_TEXT_RATIO = 4.5
const val AAA_NORMAL_TEXT_RATIO = 7.0
}
fun checkContrast(backgroundColor: Int, textColor: Int): Boolean {
val bgLuminance = getLuminance(backgroundColor)
val textLuminance = getLuminance(textColor)
val lighter = maxOf(bgLuminance, textLuminance)
val darker = minOf(bgLuminance, textLuminance)
val ratio = (lighter + 0.05) / (darker + 0.05)
return ratio >= AA_NORMAL_TEXT_RATIO
}
private fun getLuminance(color: Int): Float {
val r = Color.red(color) / 255f
val g = Color.green(color) / 255f
val b = Color.blue(color) / 255f
// 线性化 RGB
val rLinear = if (r <= 0.03928) r / 12.92f else Math.pow((r + 0.055) / 1.055, 2.4)
val gLinear = if (g <= 0.03928) g / 12.92f else Math.pow((g + 0.055) / 1.055, 2.4)
val bLinear = if (b <= 0.03928) b / 12.92f else Math.pow((b + 0.055) / 1.055, 2.4)
return 0.2126f * rLinear + 0.7152f * gLinear + 0.0722f * bLinear
}
fun getContrastRatio(backgroundColor: Int, textColor: Int): Float {
val bgLuminance = getLuminance(backgroundColor)
val textLuminance = getLuminance(textColor)
val lighter = maxOf(bgLuminance, textLuminance)
val darker = minOf(bgLuminance, textLuminance)
return (lighter + 0.05) / (darker + 0.05)
}
}6.3 动态取色 (Android 12+)
kotlin
// Android 12+ 动态取色
class DynamicColorManager {
fun applyDynamicColors(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val dynamicColors = DynamicColors.getDynamicColors(context)
// 应用动态颜色到主题
val theme = context.theme
// 主色
dynamicColors.getPrimary()?.let { primary ->
theme.applyColor(R.attr.colorPrimary, primary)
}
// 表面色
dynamicColors.getSurface()?.let { surface ->
theme.applyColor(R.attr.colorSurface, surface)
}
// 背景色
dynamicColors.getBackground()?.let { background ->
theme.applyColor(R.attr.colorBackground, background)
}
// 保存动态颜色
saveDynamicColors(dynamicColors)
}
}
private fun saveDynamicColors(dynamicColors: DynamicColors) {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val editor = prefs.edit()
dynamicColors.getPrimary()?.let { editor.putInt("dynamic_primary", it) }
dynamicColors.getSurface()?.let { editor.putInt("dynamic_surface", it) }
dynamicColors.getBackground()?.let { editor.putInt("dynamic_background", it) }
editor.apply()
}
fun restoreDynamicColors() {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val primary = prefs.getInt("dynamic_primary", 0)
val surface = prefs.getInt("dynamic_surface", 0)
val background = prefs.getInt("dynamic_background", 0)
if (primary != 0 && surface != 0 && background != 0) {
// 应用保存的动态颜色
applySavedColors(primary, surface, background)
}
}
}7. 深色模式测试策略
7.1 单元测试
kotlin
class DarkModeResourceTest {
@Test
fun testLightModeColors() {
// 测试浅色模式颜色
val lightPrimary = ContextCompat.getColor(context, R.color.primary)
assertEquals(Color.parseColor("#1976D2"), lightPrimary)
}
@Test
fun testDarkModeColors() {
// 测试深色模式颜色
val darkPrimary = ContextCompat.getColor(context, R.color.primary)
assertEquals(Color.parseColor("#90CAF9"), darkPrimary)
}
@Test
fun testColorContrast() {
// 测试颜色对比度
val bgColor = ContextCompat.getColor(context, R.color.surface)
val textColor = ContextCompat.getColor(context, R.color.on_surface)
val contrastRatio = ColorContrastChecker.getContrastRatio(bgColor, textColor)
assertTrue(contrastRatio >= 4.5f)
}
@Test
fun testThemeAttributes() {
// 测试主题属性
val primaryColor = ThemeUtils.getColor(context, R.attr.colorPrimary)
val surfaceColor = ThemeUtils.getColor(context, R.attr.colorSurface)
assertTrue(primaryColor != 0)
assertTrue(surfaceColor != 0)
}
}7.2 UI 测试
kotlin
class DarkModeUITest {
@get:Rule
val activityTestRule = ActivityScenarioRule(MainActivity::class.java)
@Test
fun testLightModeUI() {
activityTestRule.scenario.onActivity { activity ->
val rootView = activity.findViewById<View>(android.R.id.content)
val backgroundColor = getBackgroundColor(rootView)
assertEquals(Color.parseColor("#FAFAFA"), backgroundColor)
}
}
@Test
fun testDarkModeUI() {
activityTestRule.scenario.onActivity { activity ->
// 切换到深色模式
val config = activity.resources.configuration.apply {
uiMode = uiMode or Configuration.UI_MODE_NIGHT_YES
}
activity.resources.updateConfiguration(config, activity.resources.displayMetrics)
// 验证深色模式
val rootView = activity.findViewById<View>(android.R.id.content)
val backgroundColor = getBackgroundColor(rootView)
assertEquals(Color.parseColor("#121212"), backgroundColor)
}
}
@Test
fun testThemeSwitch() {
activityTestRule.scenario.onActivity { activity ->
// 获取初始背景色
val initialColor = getBackgroundColor(activity.findViewById<View>(android.R.id.content))
// 切换主题
activity.theme.applyStyle(R.style.AppTheme_Dark, true)
activity.recreate()
// 等待 Activity 重建
RoboticIdlingResource.reset()
InstrumentationRegistry.getInstrumentation()
.waitForIdleSync()
// 验证新背景色
val newColor = getBackgroundColor(activity.findViewById<View>(android.R.id.content))
assertNotEquals(initialColor, newColor)
}
}
private fun getBackgroundColor(view: View): Int {
return if (view.background is ColorDrawable) {
(view.background as ColorDrawable).color
} else {
Color.TRANSPARENT
}
}
}7.3 使用 Android Studio 预览
kotlin
// 使用 @Preview 标注
@Preview(
name = "Light Mode",
group = "DayNight",
widthDp = 360,
heightDp = 640,
uiMode = Configuration.UI_MODE_NIGHT_NO
)
@Preview(
name = "Dark Mode",
group = "DayNight",
widthDp = 360,
heightDp = 640,
uiMode = Configuration.UI_MODE_NIGHT_YES
)
@Composable
fun MyLayoutPreview() {
MyAppTheme {
MyLayout()
}
}
// 测试不同配置
@Preview(
name = "Phone Light",
widthDp = 360,
heightDp = 640,
uiMode = Configuration.UI_MODE_NIGHT_NO
)
@Preview(
name = "Phone Dark",
widthDp = 360,
heightDp = 640,
uiMode = Configuration.UI_MODE_NIGHT_YES
)
@Preview(
name = "Tablet Light",
widthDp = 600,
heightDp = 900,
uiMode = Configuration.UI_MODE_NIGHT_NO
)
@Preview(
name = "Tablet Dark",
widthDp = 600,
heightDp = 900,
uiMode = Configuration.UI_MODE_NIGHT_YES
)
@Composable
fun ResponsivePreview() {
MyAppTheme {
MyResponsiveLayout()
}
}7.4 自动化测试脚本
kotlin
// Espresso 测试深色模式
class DarkModeEspressoTest {
@Test
fun testDarkModeNavigation() {
// 1. 启动 Activity
ActivityScenario.launch(MainActivity::class.java)
// 2. 切换到深色模式
onSettingsButton().perform(click())
onDarkModeToggle().perform(click())
// 3. 验证 UI 变化
onView(withText("Dark Mode Enabled"))
.inRoot(isDisplayed())
.check(matches(isDisplayed()))
// 4. 验证颜色变化
onView(withId(R.id.container))
.check(matches(hasBackgroundColor(Color.parseColor("#121212"))))
}
@Test
fun testLightModeNavigation() {
// 1. 启动 Activity
ActivityScenario.launch(MainActivity::class.java)
// 2. 切换到浅色模式
onSettingsButton().perform(click())
onLightModeToggle().perform(click())
// 3. 验证 UI 变化
onView(withText("Light Mode Enabled"))
.inRoot(isDisplayed())
.check(matches(isDisplayed()))
}
}8. 面试考点大全
基础考点
Q1: 深色模式有哪些优势?
答案要点:
1. 降低夜间使用时的眼睛疲劳
2. 在 OLED 屏幕上更省电
3. 提升视觉舒适度和专注度
4. 符合 Material Design 设计趋势
5. 满足用户个性化需求Q2: 如何实现深色模式?
答案要点:
1. 使用 DayNight 主题
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
2. 创建 values-night 目录
res/values/colors.xml
res/values-night/colors.xml
3. 代码控制
AppCompatDelegate.setDefaultNightMode(mode)Q3: 什么是资源限定符?
答案要点:
资源限定符用于为不同配置提供不同资源:
- -night (深色模式)
- -vXX (API 版本)
- -wXXdp (最小宽度)
- -zh (中文)
示例:
res/values/colors.xml (浅色)
res/values-night/colors.xml (深色)进阶考点
Q4: 深色模式的颜色设计原则?
答案要点:
1. 使用深色背景 (#121212)
2. 使用浅色文字
3. 保持足够的对比度 (4.5:1)
4. 避免使用纯黑 (#000000)
5. 使用不同明度的灰色区分层次
6. 调整阴影效果Q5: Material Design 3 的颜色系统?
答案要点:
Material Design 3 颜色系统包括:
- Primary (主色)
- Secondary (次要色)
- Tertiary (第三色)
- Error (错误色)
- Background (背景色)
- Surface (表面色)
- Outline (轮廓色)
每个颜色都有对应的 on_* 和 container 变体。Q6: 如何测试深色模式?
答案要点:
1. 使用 Android Studio Preview
@Preview(uiMode = UI_MODE_NIGHT_YES)
2. 编写单元测试
测试颜色资源
测试对比度
3. 编写 UI 测试
使用 Espresso
验证 UI 变化高级考点
Q7: 深色模式的实现原理?
答案要点:
1. 通过 Configuration.uiMode 检测
2. 使用资源限定符加载不同资源
3. AppCompatDelegate 管理主题切换
4. ContextThemeWrapper 包装 Context
流程:
检测 UI 模式 -> 加载对应资源 -> 应用主题 -> 重建 ViewQ8: 如何实现动态取色 (Material You)?
答案要点:
Android 12+ 支持动态取色:
1. 使用 DynamicColors 类
2. 获取系统壁纸颜色
3. 生成主题色板
4. 应用到主题
代码示例:
val dynamicColors = DynamicColors.getDynamicColors(context)
dynamicColors.getPrimary()?.let { /* 应用 */ }Q9: 如何设计支持深色模式的颜色系统?
答案要点:
设计原则:
1. 使用 Material Design 颜色系统
2. 定义完整的颜色变体
3. 确保对比度符合 WCAG 标准
4. 考虑色盲用户
5. 提供降级方案
实现方式:
- values/colors.xml (浅色)
- values-night/colors.xml (深色)
- 使用主题属性引用Q10: 深色模式的性能优化?
答案要点:
优化策略:
1. 避免在运行时计算颜色
2. 使用资源限定符
3. 预加载深色模式资源
4. 减少不必要的重建
5. 使用 View 复用
注意事项:
- 深色模式切换可能触发配置变更
- 避免在 onConfigurationChanged 中做耗时操作
- 使用正确的资源限定符组合9. 参考资料
官方文档
推荐资源
本文完,感谢阅读!