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

Compose应用主题切换无响应,无法触发Activity重建/重组

问题:Compose 主题切换无法实时触发重组/Activity重建

我开发的应用包含一个Dialog,支持在深色与浅色主题间切换。此前基于XML+Fragment开发时主题切换功能正常,但迁移到Compose后,主题变更无法触发Activity重建。偏好设置修改是有效的——切换主题后重启应用,主题会正确生效,但就是无法实时触发Activity重建或重组。

相关代码

UserSettings类

class UserSettings @Inject constructor(@ApplicationContext context: Context) {
 private val preferences by lazy {
        context.getSharedPreferences(PARCELLIST_PREFS_KEY, Context.MODE_PRIVATE)
    }

    override var theme: ThemeMode by ThemeModePreferenceDelegate(PREFERENCE_NIGHT_MODE, ThemeMode.SYSTEM)

    override val themeModeStream: MutableStateFlow<ThemeMode> = MutableStateFlow(theme)


    inner class ThemeModePreferenceDelegate(
        private val name: String,
        private val default: ThemeMode,
    ) : ReadWriteProperty<Any?, ThemeMode> {

        override fun getValue(thisRef: Any?, property: KProperty<*>): ThemeMode =
            ThemeMode.fromOrdinal(preferences.getInt(name, default.ordinal))

        override fun setValue(thisRef: Any?, property: KProperty<*>, value: ThemeMode) {
            themeModeStream.value = value
            preferences.edit { putInt(name, value.ordinal) }
        }
    }
    
    companion object {
        const val PARCELLIST_PREFS_KEY = "ParcelList"
        private const val PREFERENCE_NIGHT_MODE = "preference_night_mode"
    }
}

Activity的onCreate方法

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val theme = userSettings.themeModeStream.collectAsState()
            Timber.w("THEME - Theme is $theme")
            val useDarkColors = when (theme.value) {
                ThemeMode.SYSTEM -> isSystemInDarkTheme()
                ThemeMode.LIGHT -> false
                ThemeMode.DARK -> true
            }
            val windowSizeClass = calculateWindowSizeClass(this)
            MyTheme(useDarkColors) {
                MyApp(
                    windowSizeClass
                )
            }
        }
}

MyTheme组合函数

fun MyTheme(
    useDarkTheme: Boolean = isSystemInDarkTheme(),
    overrideTypography: Typography? = null,
    overrideColors: ColorScheme? = null,
    content: @Composable () -> Unit,
) {
    val colors = overrideColors ?: if (!useDarkTheme) {
        LightColors
    } else {
        DarkColors
    }
    val typography = overrideTypography ?: MaterialTheme.typography
    MaterialTheme(
        colorScheme = colors,
        content = content,
        typography = typography
    )
}

问题原因与修复方案

核心问题

  1. isSystemInDarkTheme()仅在Compose初始组合时计算一次,后续系统主题变化不会自动触发重组,因为它不是可观察状态。
  2. 虽然theme通过collectAsState()实现了可观察,但useDarkColors的计算逻辑没有绑定到系统主题的变化上,导致主题切换后无法触发重组。
  3. XML时代依赖Activity重建切换主题,但Compose应通过重组实现实时切换,无需重建Activity。

修复步骤

1. 让系统主题变化可观察

创建可监听系统主题变化的Compose状态:

@Composable
fun isSystemInDarkThemeFlow(): State<Boolean> {
    val configuration = LocalConfiguration.current
    val systemDarkTheme = remember { mutableStateOf(isSystemInDarkTheme()) }
    
    SideEffect {
        val listener = object : Configuration.OnConfigurationChangedListener {
            override fun onConfigurationChanged(newConfig: Configuration) {
                systemDarkTheme.value = isSystemInDarkTheme()
            }
        }
        configuration.registerComponentCallbacks(listener)
        // 组件销毁时清理回调
        DisposableEffect(Unit) {
            onDispose {
                configuration.unregisterComponentCallbacks(listener)
            }
        }
    }
    return systemDarkTheme
}

2. 修改Activity中的主题计算逻辑

更新onCreate内的代码,让useDarkColors成为可观察状态:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        val themeMode = userSettings.themeModeStream.collectAsState()
        val systemDarkTheme = isSystemInDarkThemeFlow()
        
        // 当主题模式或系统主题变化时,重新计算useDarkColors
        val useDarkColors = remember(themeMode.value, systemDarkTheme.value) {
            when (themeMode.value) {
                ThemeMode.SYSTEM -> systemDarkTheme.value
                ThemeMode.LIGHT -> false
                ThemeMode.DARK -> true
            }
        }
        
        Timber.w("THEME - 当前主题模式: ${themeMode.value}, 深色模式启用: $useDarkColors")
        
        val windowSizeClass = calculateWindowSizeClass(this)
        MyTheme(useDarkColors) {
            MyApp(windowSizeClass)
        }
    }
}

3. 确保UserSettings的流初始化正确

在UserSettings构造函数中添加初始化逻辑,保证流的初始值与SharedPreferences同步:

class UserSettings @Inject constructor(@ApplicationContext context: Context) {
    // ... 原有代码 ...
    
    init {
        // 同步SharedPreferences中的主题值到流
        themeModeStream.value = theme
    }
    
    // ... 原有代码 ...
}

4. 可选:强制Activity重建(不推荐Compose场景)

如果想沿用XML时代的重建方式,在主题切换的Dialog点击事件中调用:

// 切换主题时执行
userSettings.theme = newThemeMode
recreate()

注意:此方式会导致界面闪烁,性能损耗大,优先推荐Compose重组方案。

验证要点

  • 切换主题后,检查Timber日志是否输出新的主题值
  • 确认useDarkColors变化会触发MyTheme重组,进而更新MaterialTheme的colorScheme

内容的提问来源于stack exchange,提问作者M Rajoy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 04:17:13