Compose 深色模式完全指南:主题切换与动态适配

2024-05-28 · 24 min · 主题

深色模式已成为现代应用的标配功能,不仅能减少眼睛疲劳、节省电量,还能提供更好的视觉体验。本文将深入讲解如何在 Compose 中实现完善的深色模式支持。

一、检测系统深色模式

@Composable
fun App() {
    val isDarkTheme = isSystemInDarkTheme()
    
    MyAppTheme(darkTheme = isDarkTheme) {
        MainContent()
    }
}

二、用户可控的主题切换

enum class ThemeMode { LIGHT, DARK, SYSTEM }

@Composable
fun App(viewModel: ThemeViewModel) {
    val themeMode by viewModel.themeMode.collectAsState()
    val systemDarkTheme = isSystemInDarkTheme()
    
    val isDarkTheme = when (themeMode) {
        ThemeMode.LIGHT -> false
        ThemeMode.DARK -> true
        ThemeMode.SYSTEM -> systemDarkTheme
    }
    
    MyAppTheme(darkTheme = isDarkTheme) {
        MainContent()
    }
}

三、Material You 动态颜色

@Composable
fun MyAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) 
            else dynamicLightColorScheme(context)
        }
        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }
    
    MaterialTheme(colorScheme = colorScheme, content = content)
}

四、DataStore 持久化

class ThemePreferences(private val dataStore: DataStore<Preferences>) {
    
    val themeMode: Flow<ThemeMode> = dataStore.data
        .map { preferences ->
            val modeString = preferences[THEME_MODE_KEY] ?: ThemeMode.SYSTEM.name
            ThemeMode.valueOf(modeString)
        }
    
    suspend fun setThemeMode(mode: ThemeMode) {
        dataStore.edit { it[THEME_MODE_KEY] = mode.name }
    }
}

五、语义化颜色

// ✅ 好:使用 MaterialTheme 颜色
Card(
    colors = CardDefaults.cardColors(
        containerColor = MaterialTheme.colorScheme.surface,
        contentColor = MaterialTheme.colorScheme.onSurface
    )
)

// ❌ 避免:硬编码颜色
Card(
    colors = CardDefaults.cardColors(
        containerColor = Color.White  // 深色模式下会很刺眼
    )
)

💡 最佳实践

始终使用 MaterialTheme.colorScheme 中的颜色,避免硬编码。这样可以确保应用在深色和浅色模式下都能正确显示。

六、Preview 预览两种主题

@Preview(name = "Light Mode")
@Preview(name = "Dark Mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun ScreenPreview() {
    MyAppTheme {
        MyScreen()
    }
}

七、最佳实践

总结