kotlin 喷气背包组合:如何以编程方式将主题从亮模式更改为暗模式onClick

btxsgosb  于 2022-12-04  发布在  Kotlin
关注(0)|答案(5)|浏览(135)

TL;DR更改主题,并在亮主题和暗主题之间重新组合应用程序onClick。
你好!我有一个有趣的问题,我一直在努力弄清楚,并希望得到一些帮助。我正在尝试实现一个设置屏幕,让用户改变应用程序的主题(选择黑暗,光明,或自动匹配系统设置)。
我在选择调色板时通过调用isSystemInDarkTheme()函数成功地动态设置了主题,但在点击一个按钮时重新组合应用的明暗主题时却很吃力。
我现在的策略是创建一个主题模型,它从用户实际选择主题的设置组件中提升状态。然后,这个主题模型向自定义主题公开一个主题状态变量(围绕材质主题),以决定是选择浅色还是深色调色板。
布景主题

@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组件中省略了一些代码,可能是我在此过程中遗漏了一些闭包语法。

pn9klfpd

pn9klfpd1#

如Jetpack主题化代码实验室所示,一种可能性是通过输入参数设置darkmode,这确保了当参数改变时,主题将被重新组合:

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

在mainActivity中,您可以观察viewModel的变化,并将其传递给customTheme:

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

CustomTheme(darkTheme.value){
//yourContent
}

这样,您的合成预览就可以简单地以暗色主题进行样式化:

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

u5i3ibmn2#

如果您希望按钮/开关更改主题并使其作为设置持久化,您也可以通过使用Jetpack DataStore(推荐)或SharedPreferences来实现这一点,在MainActivity中获取主题状态并将其传递给您的Theme组合对象,以及您希望修改它的任何位置。
您可以在GitHub repo中找到SharedPreferences的完整工作示例。
此示例使用SingletonHilt作为依赖项,并且对您要存储的所有首选项都有效。

hyrbngr7

hyrbngr73#

基于the docs,处理由用户操作触发的主题更改(即,通过自定义构建的设置选择主题而不是系统主题)的正式方式是使用

AppCompatDelegate.setDefaultNightMode()

这个调用本身就可以处理大多数事情,包括重新启动任何活动(因此,重新组合)。

  • 调用setContent以扩展AppCompatActvity的活动
  • 用户选择在每次启动应用程序时保留和应用(通过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执行上述所有操作,提供更多详细信息。

jaxagkaj

jaxagkaj4#

这可能不是推荐的方法,但有一种选择是使用recreate方法(自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
    }
}
ekqde3dh

ekqde3dh5#

我只是用一个简单的模型做出来的。
声明公共变量isUiModeDark

lateinit var isUiModeDark : MutableState<Boolean?>

然后,在主题的Lambda中:

isUiModeIsDark = remember { mutableStateOf(userSelectedThemeIsNight(context)) }
val colorScheme = when (isUiModeIsDark.value) {
    true -> DarkColorScheme
    false -> LightColorScheme
    else -> if (systemTheme) DarkColorScheme else LightColorScheme
}
.
.
.
MaterialTheme(
    colorScheme = colorScheme,
    content = content
)

现在如何调用recomposition?很简单,只需在设置中更改isUiModeDark的值。

  • true表示已选择暗主题应用覆盖
  • false表示已选择灯光主题应用覆盖
  • null表示未选择主题应用程序覆盖并遵循系统主题。

至于为什么这样做,只要阅读MutableState上的文档并记住。基本上,MutableState值的任何更改都会导致重新组合。Live数据或任何其他替代项都不起作用,除非它们是State类型的派生项。可组合项基本上只是订阅了对MutableState或State类的任何派生项所做的更改。

相关问题