Jetpack Compose:如何以编程方式将主题从浅色模式更改为深色模式 onClick

Jetpack Compose: How to change theme from light to dark mode programmatically onClick

TL;DR 更改主题并在点击时在浅色和深色主题之间重组应用程序。

您好!我有一个有趣的问题,我一直在努力弄清楚,希望得到一些帮助。我正在尝试实现一个设置屏幕,让用户可以更改应用程序的主题(选择与系统设置匹配的深色、浅色或自动)。

我在选择调色板时通过调用 isSystemInDarkTheme() 函数成功地 动态 设置了主题,但我正在努力 重构 只需单击一个按钮即可在明暗主题之间切换应用程序。

我现在的策略是创建一个主题模型,它从用户实际选择主题的设置组件中提升状态。然后这个主题模型将主题状态变量暴露给自定义主题(环绕 material theme) 来决定是选择浅色还是深色调色板。这是相关代码 -->

主题

@Composable
fun CustomTheme(
themeViewModel: ThemeViewModel = viewModel(),
content: @Composable() () -> Unit,
) {
   val colors = when (themeViewModel.theme.value.toString()) {
       "Dark" -> DarkColorPalette
       "Light" -> LightColorPalette
       else -> if (isSystemInDarkTheme()) DarkColorPalette else LightColorPalette
   }

   MaterialTheme(
       colors = colors,
       typography = typography,
       shapes = shapes,
       content = content
   )
   }

主题模型和状态变量

class ThemeViewModel : ViewModel() {
private val _theme = MutableLiveData("Auto")
val theme: LiveData<String> = _theme

fun onThemeChanged(newTheme: String) {
    when (newTheme) {
        "Auto" -> _theme.value = "Light"
        "Light" -> _theme.value = "Dark"
        "Dark" -> _theme.value = "Auto"
    }
}
}

组件(UI)代码

@Composable
fun Settings(
   themeViewModel: ThemeViewModel = viewModel(),
) {
   ...
   val theme: String by themeViewModel.theme.observeAsState("")
   ...
   ScrollableColumn(Modifier.fillMaxSize()) {
       Column {
        ...
        Card() {
            Row() {
                Text(text = theme,
                    modifier = Modifier.clickable(
                        onClick = {
                            themeViewModel.onThemeChanged(theme)
                        }
                    )
                )
            }
        }
   }

非常感谢您的宝贵时间和帮助! ***我在 UI 组件中省略了一些代码,可能我在这个过程中遗漏了一些闭包语法。

Jetpack theming codelab中显示的一种可能性是通过输入参数设置暗模式,以确保在参数更改时重新组合主题:

@Composable
fun CustomTheme(
  darkTheme: Boolean = isSystemInDarkTheme(),
  content: @Composable () -> Unit
) {
  MaterialTheme(
    colors = if (darkTheme) DarkColors else LightColors,
    content = content
  )
}

在您的 mainActivity 中,您可以观察对 viewModel 的更改并将它们传递给您的自定义主题:

val darkTheme = themeViewModel.darkTheme.observeAsState(initial = true)

CustomTheme(darkTheme.value){
//yourContent
}

这样您的撰写预览就可以简单地设置为深色主题:

@Composable
private fun DarkPreview() {
    CustomTheme(darkTheme = true) {
        content
    }
}

如果您希望 button/switch 更改主题并使其作为设置持久化,您也可以通过使用 Jetpack DataStore (recommended) or SharedPreferences 来实现,在 MainActivity 中获取您的主题状态并将其传递给您的主题可组合,随心所欲修改。

您可以在 GitHub repo 中找到 SharedPreferences 的完整工作示例。

此示例使用 SingletonHilt 作为依赖项,并且对您要存储的所有首选项都有效。

这可能不是推荐的方式,但一种选择是使用 recreate method(自 API 级别 11 起可用)。

为了在 activity 之外和可组合项内使用它,您可以传递函数调用。以你的代码为基础

class SomeActivity {
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        setContent {
            ...
            themeViewModel.onRecreate = { recreate() }

            CustomTheme(themeViewModel) {
                ...
            }
        }
    }
}

在你的视图模型中

class ThemeViewModel: ViewModel() {

    lateinit var onRecreate: () -> Unit

    private val _theme = MutableLiveData("Auto")
    val theme: LiveData<String> = _theme
    
    fun onThemeChanged(newTheme: String) {
        when (newTheme) {
            "Auto" -> _theme.value = "Light"
            "Light" -> _theme.value = "Dark"
            "Dark" -> _theme.value = "Auto"
        }
        onRecreate
    }
}

基于 the docs, the official way to handle theme changes 由用户操作触发(即通过自定义设置选择系统主题以外的主题)是使用

AppCompatDelegate.setDefaultNightMode()

这个调用本身就可以处理大部分事情,包括重新启动任何 activity(因此,重新组合)。为此,我们需要:

  • 调用setContent扩展的ActivityAppCompatActvity
  • 每次启动应用程序时(通过 AppCompatDelegate)要保留和应用的用户选择
  • 要定义是否启用深色模式,您的 CustomTheme 还应考虑用户 defaultNightMode 偏好的值:
@Composable
fun CustomTheme(
  isDark: Boolean = isNightMode(),
  content: @Composable () -> Unit
) {
  MaterialTheme(
    colors = if (darkTheme) DarkColors else LightColors,
    content = content
  )
}

@Composable
private fun isNightMode() = when (AppCompatDelegate.getDefaultNightMode()) {
    AppCompatDelegate.MODE_NIGHT_NO -> false
    AppCompatDelegate.MODE_NIGHT_YES -> true
    else -> isSystemInDarkTheme()
}

这很好,因为它避免了在 Activity 中获取此值的需要,只是为了将它传递给 CustomTheme(isDark = isDark) 的主题。

This article 遍历以上所有内容,提供更多详细信息。