Skip to content

深色模式适配深度指南

字数统计:约 10000 字
难度等级:⭐⭐⭐⭐⭐
面试重要度:⭐⭐⭐⭐⭐


目录

  1. 深色模式概述
  2. styles.xml 配置详解
  3. themes.xml 配置详解
  4. 资源限定符完整指南
  5. 组件适配最佳实践
  6. 颜色系统设计
  7. 深色模式测试策略
  8. 面试考点大全
  9. 参考资料

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.xml

4.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 模式 -> 加载对应资源 -> 应用主题 -> 重建 View

Q8: 如何实现动态取色 (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. 参考资料

官方文档

推荐资源


本文完,感谢阅读!