有一个 mainActivity,其中调用 navHost 并在其中调用抽屉,并从中调用设置屏幕
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
setContent {
val viewModel: SettingsViewModel = hiltViewModel()
val activityState by viewModel.appTheme.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
viewModel.getTheme(this@MainActivity)
}
AppTheme(
isDarkTheme = shouldUseDarkTheme(appTheme = activityState)
) {
TopNavHost()
}
}
}
}
用于设置的viewModel:
class SettingsViewModel: ViewModel() {
private val _appTheme = MutableStateFlow(AppTheme.System)
val appTheme: StateFlow<AppTheme> = _appTheme
fun getTheme(context: Context) {
viewModelScope.launch {
_appTheme.value = withContext(Dispatchers.IO) {
DataStoreManager(context).getTheme()
}
}
}
fun saveTheme(context: Context, newTheme: AppTheme) {
_appTheme.value = newTheme
viewModelScope.launch {
DataStoreManager(context).saveTheme(_appTheme.value.name)
}
}
}
以及设置屏幕本身
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsScreen(
onBack: () -> Unit,
viewModel: SettingsViewModel = hiltViewModel()
) {
val context = LocalContext.current
LaunchedEffect(Unit) {
viewModel.getTheme(context)
}
Scaffold(topBar = {
TopAppBar(
title = {
Text(
text = stringResource(id = R.string.settings),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
navigationIcon = {
IconButton(onClick = { onBack() }) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "back"
)
}
}
)
},
content = { innerPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.padding(horizontal = 10.dp)
) {
ThemeSettings(viewModel.appTheme.collectAsStateWithLifecycle().value) {
viewModel.saveTheme(context, it)
}
HorizontalDivider(
modifier = Modifier.padding(top = 5.dp, bottom = 10.dp),
thickness = 1.dp
)
LanguageSettings()
HorizontalDivider(
modifier = Modifier.padding(top = 5.dp, bottom = 10.dp),
thickness = 1.dp
)
}
})
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun ThemeSettings(appTheme: AppTheme, onAppThemeChanged: (AppTheme) -> Unit) {
val themes = arrayOf(
stringResource(id = R.string.system_theme),
stringResource(id = R.string.dark_theme),
stringResource(id = R.string.light_theme)
)
Column(
modifier = Modifier.fillMaxWidth()
) {
Spacer(modifier = Modifier.height(5.dp))
Text(text = stringResource(id = R.string.theme))
FlowRow(
modifier = Modifier.fillMaxWidth()
) {
TextedRadioButton(
selected = appTheme == AppTheme.System,
onClick = {
if (appTheme != AppTheme.System)
onAppThemeChanged(AppTheme.System)
},
text = themes[0]
)
TextedRadioButton(
selected = appTheme == AppTheme.Light,
onClick = {
if (appTheme != AppTheme.Light)
onAppThemeChanged(AppTheme.Light)
},
text = themes[2]
)
TextedRadioButton(
selected = appTheme == AppTheme.Dark,
onClick = {
if (appTheme != AppTheme.Dark)
onAppThemeChanged(AppTheme.Dark)
},
text = themes[1]
)
}
}
}
如果我直接从 MainActivity 调用 settingsScreen,则主题会更改而无需重新进入应用程序。 我知道我的 MainActivity 仅在应用程序打开时启动,但我不明白如何直接从设置屏幕更改主题。
这是主题代码:
@Composable
fun AppTheme(
isDarkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = if (isDarkTheme) DarkColors else LightColors
if (Build.VERSION.SDK_INT >= 29) {
LocalView.current.isForceDarkAllowed = false
}
val systemUiController = rememberSystemUiController()
SideEffect {
systemUiController.setStatusBarColor(
color = Color.Transparent,
darkIcons = !isDarkTheme
)
systemUiController.setNavigationBarColor(
color = Color.Transparent,
darkIcons = !isDarkTheme
)
}
MaterialTheme(
colorScheme = colorScheme,
content = {
Surface(
modifier = Modifier.fillMaxSize(),
color = colorScheme.background,
contentColor = colorScheme.onBackground,
content = content
)
}
)
}
根据您提供的代码,看起来有两个原因:
1。您正在使用 settingsViewModel 的两个实例
在您的代码中,仅针对
_appTheme.value = newTheme
上的 SettingsViewModel
实例调用 SettingsScreen
,另一个(在 MainActivity
内)保持不变。
这没有什么问题,但您的
DataStoreManager
应该提供如何在多个屏幕上共享单个AppTheme
的方法。
2。您没有观察到您的
AppTheme
的变化。
在您的代码中,在
AppTheme
中获得 LaunchedEffect
,这是单次操作(在您的代码中,而不是一般情况下)。这就是为什么只有当您再次进入应用程序时它才起作用。
解决方案
我不知道你的
DataStoreManager
,所以我创建了这个。使用 hilt
@Singleton
确保您只有一个实例。
@Singleton
class DataStoreManager @Inject constructor(
@ApplicationContext
private val context: Context
) {
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "test data store")
val key = stringPreferencesKey("appTheme")
//Flow that gets updated every time you update theme
val themeFlow = context.dataStore.data.map { AppTheme.byName(it[key] ?: "") }
suspend fun saveTheme(name: String) {
context.dataStore.edit {
it[key] = name
}
}
}
通过此功能,您可以收集
AppTheme
中的 SettingsViewModel
更改并进一步发送。现在不需要getTheme
功能,主题正在更新中。
@HiltViewModel
class SettingsViewModel @Inject constructor(
context: Application,
val dataStore: DataStoreManager
) : ViewModel() {
private val _appTheme = MutableStateFlow(AppTheme.System)
val appTheme: StateFlow<AppTheme> = _appTheme
init {
viewModelScope.launch {
//Collecting theme
dataStore.themeFlow.collect { newTheme ->
_appTheme.value = newTheme
}
}
}
fun saveTheme(newTheme: AppTheme) {
//Now this is not necessary because once you save theme
//_appTheme gets updated
//_appTheme.value = newTheme
viewModelScope.launch {
dataStore.saveTheme(newTheme.name)
}
}
}
这样,您的主题更改将立即可见。