updateConfiguration后如何触发重组?

sla*_*boy 7 android kotlin android-jetpack-compose android-jetpack-compose-text

我想动态更改我的应用程序语言,而不需要重新启动以Activity使结果生效。我现在要做的是添加一个可变Boolean状态,它是 switch 并由所有Text元素使用。

在此输入图像描述

要更改语言,我在可单击回调中调用以下代码(我使用该框作为虚拟对象,只是为了测试):

val configuration = LocalConfiguration.current
val resources = LocalContext.current.resources
Box( 
    modifier = Modifier
        .fillMaxWidth()
        .height(0.2.dw)
        .background(Color.Red)
        .clickable {
            
            // to change the language
            val locale = Locale("bg")
            configuration.setLocale(locale)
            resources.updateConfiguration(configuration, resources.displayMetrics)
            viewModel.updateLanguage()
        }
) {
}
Run Code Online (Sandbox Code Playgroud)

updateLanguage()然后它使用该方法切换语言值

@HiltViewModel
class CityWeatherViewModel @Inject constructor(
    private val getCityWeather: GetCityWeather
) : ViewModel() {

    private val _languageSwitch = mutableStateOf(true)
    var languageSwitch: State<Boolean> = _languageSwitch

    fun updateLanguage() {
        _languageSwitch.value = !_languageSwitch.value
    }
}
Run Code Online (Sandbox Code Playgroud)

问题是,为了更新每个Text可组合项,我需要将 传递viewmodel给所有使用的后代Text,然后使用一些错误的逻辑来强制更新每次视图模型中发生的一些更改。

@Composable
fun SomeChildDeepInTheHierarchy(viewModel: CityWeatherViewModel, @StringRes textResId: Int) {
 
    Text(
        text = stringResource(id = if (viewModel.languageSwitch.value) textResId else textResId),
        color = Color.White,
        fontSize = 2.sp,
        fontWeight = FontWeight.Light,
        fontFamily = RobotoFont
    )
}
Run Code Online (Sandbox Code Playgroud)

它可以工作,但是这是一些非常糟糕的逻辑,并且代码非常丑陋!是否有动态更改 Jetpack Compose 使用的标准方法Locale

Phi*_*hov 10

最简单的解决方案是在配置更改后重新创建活动:

val context = LocalContext.current
Button({
    // ...
    resources.updateConfiguration(configuration, resources.displayMetrics)
    context.findActivity()?.recreate()
}) {
    Text(stringResource(R.string.some_string))
}
Run Code Online (Sandbox Code Playgroud)

findActivity:

fun Context.findActivity(): Activity? = when (this) {
    is Activity -> this
    is ContextWrapper -> baseContext.findActivity()
    else -> null
}
Run Code Online (Sandbox Code Playgroud)

如果由于某种原因您不想这样做,您可以LocalContext使用新配置进行覆盖,如下所示:

MainActivity:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            val context = LocalContext.current
            CompositionLocalProvider(
                LocalMutableContext provides remember { mutableStateOf(context) },
            ) {
                CompositionLocalProvider(
                    LocalContext provides LocalMutableContext.current.value,
                ) {
                    // your app
                }
            }
        }
    }
}

val LocalMutableContext = staticCompositionLocalOf<MutableState<Context>> {
    error("LocalMutableContext not provided")
}
Run Code Online (Sandbox Code Playgroud)

在您看来:

val configuration = LocalConfiguration.current
val context = LocalContext.current
val mutableContext = LocalMutableContext.current
Button(onClick = {
    val locale = Locale(if (configuration.locale.toLanguageTag() == "bg") "en_US" else "bg")
    configuration.setLocale(locale)
    mutableContext.value = context.createConfigurationContext(configuration)
}) {
    Text(stringResource(R.string.some_string))
}
Run Code Online (Sandbox Code Playgroud)

请注意,这remember不会经历系统配置更改(例如屏幕旋转),您可能需要将选定的区域设置存储在某处,例如在 中,并在提供 时DataStore提供所需的配置而不是我的初始配置。contextLocalMutableContext

ps 在这两种情况下,您不需要在视图模型中添加标志,如果您根据文档放置了资源,例如在values-bg/strings.xml等中,stringResource则可以开箱即用。