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
的完整工作示例。
此示例使用 Singleton
和 Hilt 作为依赖项,并且对您要存储的所有首选项都有效。
这可能不是推荐的方式,但一种选择是使用 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 遍历以上所有内容,提供更多详细信息。
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
的完整工作示例。
此示例使用 Singleton
和 Hilt 作为依赖项,并且对您要存储的所有首选项都有效。
这可能不是推荐的方式,但一种选择是使用 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 遍历以上所有内容,提供更多详细信息。