通过编程切换明暗主题:实现全局可设置的主题状态变量
问题分析与解决方案
现有代码的问题
- 仅在ViewModel初始化时获取一次系统主题状态,系统主题后续变化时不会自动同步到
isDarkTheme - 用户手动切换的主题没有持久化,重启App后会重置为系统主题
- 未处理全局调用的ViewModel生命周期问题,普通ViewModel绑定页面生命周期,无法真正全局可用
修正后的实现方案
1. 依赖准备(如需)
如果用DataStore做持久化,先添加依赖:
// build.gradle(Module) dependencies { implementation("androidx.datastore:datastore-preferences:1.0.0") // ViewModel相关依赖 implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") }
2. 全局单例ThemeViewModel
使用Application作为ViewModel的宿主,确保全局唯一且生命周期和App一致:
class ThemeViewModel(application: Application) : AndroidViewModel(application) { // 初始化DataStore private val dataStore = application.createDataStore(name = "theme_preferences") private val THEME_KEY = booleanPreferencesKey("is_dark_theme") // 监听系统主题变化的流 private val systemThemeFlow = callbackFlow { val receiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { trySend(isSystemInDarkTheme()) } } // 注册系统配置变化广播 application.registerReceiver( receiver, IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED) ) // 初始发送一次系统主题状态 trySend(isSystemInDarkTheme()) // 取消注册 awaitClose { application.unregisterReceiver(receiver) } } // 用户持久化的主题设置流 private val userThemeFlow = dataStore.data .map { preferences -> preferences[THEME_KEY] // null表示跟随系统 } // 对外暴露的最终主题状态流:优先使用用户设置,无设置则跟随系统 val isDarkTheme: StateFlow<Boolean> = combine(userThemeFlow, systemThemeFlow) { userTheme, systemTheme -> userTheme ?: systemTheme }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = isSystemInDarkTheme() ) // 全局调用的主题设置方法:支持三种模式 fun setTheme(themeMode: ThemeMode) { viewModelScope.launch { when (themeMode) { ThemeMode.FOLLOW_SYSTEM -> dataStore.edit { it.remove(THEME_KEY) } ThemeMode.DARK -> dataStore.edit { it[THEME_KEY] = true } ThemeMode.LIGHT -> dataStore.edit { it[THEME_KEY] = false } } } } // 快捷切换主题的方法:跟随系统→深色→浅色循环 fun toggleTheme() { viewModelScope.launch { val currentUserTheme = userThemeFlow.first() val newTheme = when (currentUserTheme) { null -> ThemeMode.DARK true -> ThemeMode.LIGHT false -> ThemeMode.FOLLOW_SYSTEM } setTheme(newTheme) } } } // 主题模式枚举定义 enum class ThemeMode { FOLLOW_SYSTEM, DARK, LIGHT }
3. 获取全局ViewModel的方式
在任意页面或组件中获取全局ViewModel:
val themeViewModel = ViewModelProvider( ApplicationProvider.getApplicationContext<MyApplication>(), ViewModelProvider.AndroidViewModelFactory.getInstance(ApplicationProvider.getApplicationContext()) )[ThemeViewModel::class.java]
如果使用Hilt,可通过依赖注入简化:
@HiltViewModel class ThemeViewModel @Inject constructor( @ApplicationContext private val context: Context, private val dataStore: DataStore<Preferences> ) : ViewModel() { // 内部实现逻辑同上,依赖注入替代手动初始化 } // 在任意注入点直接获取 @Inject lateinit var themeViewModel: ThemeViewModel
关键优化点说明
- 系统主题实时同步:通过广播监听系统配置变化,自动更新主题状态
- 偏好持久化:用DataStore保存用户主题设置,重启App后不会丢失
- 全局生命周期:基于Application的ViewModel确保全局可用,不受页面销毁影响
- 状态优先级:用户自定义设置优先于系统主题,无自定义时自动跟随系统
内容的提问来源于stack exchange,提问作者ImEmo
相关产品推荐
相关产品推荐

