You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

通过编程切换明暗主题:实现全局可设置的主题状态变量

问题分析与解决方案

现有代码的问题

  • 仅在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 18:13:24