android 如何在编写中使用导航时保存芯片内的UI状态?

lf5gs5x2  于 2023-05-05  发布在  Android
关注(0)|答案(1)|浏览(151)

我有一个芯片,当被点击时会改变配置。然而,当用户导航返回(popbackstack)或导航到另一个屏幕时,点击的芯片重置其先前的位置。据我所知,我将不得不使用一个视图模型来传递芯片状态,并以这种方式保存它?我找不到一种方法,虽然存储“记忆保存”在一个视图模型。
我如何才能做到这一点?感谢任何反馈!
我的示例芯片:

@Composable
fun CatsChip() {

    val textChipRememberOneState = rememberSaveable { mutableStateOf(false) }

    TextChip(
        isSelected = textChipRememberOneState.value,
        shape = Shapes(medium = RoundedCornerShape(15.dp)),
        text = "Cats",
        selectedColor = LightGreen,
        onChecked = {
            textChipRememberOneState.value = it
        },
    )
}
khbbv19g

khbbv19g1#

您可以将MutableStateFlow中的状态保存在ViewModel中。
要在Compose中使用ViewModel s,需要将以下依赖项添加到app/build.gradle文件中

implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")

现在,您可以使用viewModel()函数在可组合对象中获取ViewModel示例。
有了ViewModel s,你就不需要再使用rememberSaveable了,因为状态会保存在ViewModel中,但是,如果你想让状态在进程死亡(而不仅仅是配置更改)后仍然存在,那么你必须将状态保存在SavedStateHandle中。
下面是一个ViewModel的例子,它只将状态保存在内存中,但不将其保存在SavedStateHandle中。

class MemoryOnlyViewModel : ViewModel () {
    val checkedState = MutableStateFlow(false)

    fun onCheckedChange(isChecked: Boolean) = checkedState.update { isChecked }
}

下面是一个ViewModel的例子,它将状态保存在SavedStateHandle中。

class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() {
    val checkedState = state.getStateFlow(key = CHECKED_STATE_KEY, initialValue = false)

    fun onCheckedChange(isSelected: Boolean) = state.set(key = CHECKED_STATE_KEY, value = isSelected)

    companion object {
        private const val CHECKED_STATE_KEY = "checkedState"
    }
}

那么用法如下所示

import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
        
@Composable
fun CatsChip() {
    val vm: SavedStateViewModel = viewModel() // or: val vm = viewModel<SavedStateViewModel>()
    val catsChipState by vm.checkedState.collectAsState()

    TextChip(
        isSelected = catsChipState,
        shape = Shapes(medium = RoundedCornerShape(15.dp)),
        text = "Cats",
        selectedColor = LightGreen,
        onChecked = vm::onCheckedChange, // or: onChecked = { vm.onCheckedChange(it) }
    )
}

有关更多用例,请参见撰写状态提升文档的业务逻辑部分
下面是一个使用Compose导航的Composable演示,展示了上面的两个视图模型,将其与rememberSaveable进行了比较,并以两种不同的方式将其范围划分到父上下文和NavBackStackEntry。这显示了不同的作用域如何影响ViewModel s的生命周期。
app/build.gradle文件中需要Compose导航依赖项

implementation("androidx.navigation:navigation-compose:2.5.3")

您可以通过在应用程序的某些可组合内容中调用Demo()来检查演示。单击按钮进行导航并查看backstack如何更改。作用域为父上下文的ViewModel s和rememberSaveable将始终保留状态,而作用域为每个NavBackStackEntryViewModel s和rememberSaveable将仅为它们自己的导航目的地保留状态,这可以在返回时看到。此外,保存在MemoryOnlyViewModel s中的状态将不会在进程死亡后继续存在,您可以通过以下方式进行检查:
1.通过按Home键将应用程序发送到后台(但不要在应用程序切换器中关闭它)
1.通过在IDE终端选项卡中使用应用程序的软件包名称运行以下命令来终止进程

adb shell am kill <package_name>

1.从应用程序切换器再次打开应用程序
如果您正确地遵循了这些步骤,并成功地以这种方式杀死和恢复了进程,那么您应该注意到只有MemoryOnlyViewModel丢失/重置了它们的状态。
下面是整个演示代码。只需复制并粘贴到一个新的Kotlin文件,并从可组合内容调用Demo()可组合。

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AddCircle
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update

class MemoryOnlyViewModel : ViewModel () {
    val checkedState = MutableStateFlow(false)

    fun onCheckedChange(isChecked: Boolean) = checkedState.update { isChecked }
}

class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() {
    val checkedState = state.getStateFlow(key = CHECKED_STATE_KEY, initialValue = false)

    fun onCheckedChange(isSelected: Boolean) = state.set(key = CHECKED_STATE_KEY, value = isSelected)

    companion object {
        private const val CHECKED_STATE_KEY = "checkedState"
    }
}
    
@Composable
fun Demo() {    
    @Composable
    fun SimpleChip(
        text: String,
        isSelected: Boolean,
        onChecked: (Boolean) -> Unit,
    ) {
        Surface(
            onClick = { onChecked(!isSelected) },
            modifier = Modifier.padding(4.dp),
            shape = RoundedCornerShape(16.dp),
            color = if (isSelected) Color(0xFF7986CB) else Color.LightGray,
        ) {
            Row(
                modifier = Modifier.padding(8.dp),
                horizontalArrangement = Arrangement.spacedBy(4.dp),
                verticalAlignment = Alignment.CenterVertically,
            ) {
                Text(text)
                Icon(
                    imageVector = if (isSelected) Icons.Default.Clear else Icons.Default.AddCircle,
                    contentDescription = null,
                )
            }
        }
    }

    // These VMs are scoped to the lifecycle of the parent context (likely a ComponentActivity)
    val parentMemoryOnlyVm: MemoryOnlyViewModel = viewModel()
    val parentSavedStateVm: SavedStateViewModel = viewModel()
    var parentSaveable by rememberSaveable { mutableStateOf(false) }

    @Composable
    @Suppress("UnusedReceiverParameter")
    fun ColumnScope.DemoScreen(text: String) {
        Text(text)

        val parentMemoryOnlyState by parentMemoryOnlyVm.checkedState.collectAsState()

        SimpleChip(text = "MemoryOnly VM (parent scoped)",
            isSelected = parentMemoryOnlyState, onChecked = parentMemoryOnlyVm::onCheckedChange)

        val navMemoryOnlyVm: MemoryOnlyViewModel = viewModel()
        val navMemoryOnlyState by navMemoryOnlyVm.checkedState.collectAsState()

        SimpleChip(text = "MemoryOnly VM (nav scoped)",
            isSelected = navMemoryOnlyState, onChecked = navMemoryOnlyVm::onCheckedChange)

        val parentSavedState by parentSavedStateVm.checkedState.collectAsState()

        SimpleChip(text = "SavedState VM (parent scoped)",
            isSelected = parentSavedState, onChecked = parentSavedStateVm::onCheckedChange)

        val navSavedStateVm: SavedStateViewModel = viewModel()
        val navSavedState by navSavedStateVm.checkedState.collectAsState()

        SimpleChip(text = "SavedState VM (nav scoped)",
            isSelected = navSavedState, onChecked = navSavedStateVm::onCheckedChange)

        SimpleChip(text = "rememberSaveable (parent scoped)",
            isSelected = parentSaveable, onChecked = { parentSaveable = it })

        var navSaveable by rememberSaveable { mutableStateOf(false) }

        SimpleChip(text = "rememberSaveable (nav scoped)",
            isSelected = navSaveable, onChecked = { navSaveable = it })
    }

    val navController = rememberNavController()

    @Composable
    fun BackButton() = Button(onClick = { navController.navigateUp() }) {
        Text("Go back")
    }

    @Composable
    fun NavButton(route: String) = Button(onClick = { navController.navigate(route) }) {
        Text("Navigate to $route")
    }

    Column {
        val currentEntry by navController.currentBackStackEntryAsState()
        val backStack = remember(currentEntry) {
            navController.backQueue
                .mapNotNull { it.destination.route }
                .joinToString(" > ")
        }
        Text(text = "Backstack: $backStack")

        NavHost(navController = navController, startDestination = "start") {
            composable("start") {
                Row { NavButton(route = "A"); NavButton(route = "B") }
            }
            composable("A") {
                Column {
                    Row { BackButton(); NavButton(route = "B") }
                    DemoScreen("Screen A")
                }
            }
            composable("B") {
                Column {
                    Row { BackButton(); NavButton(route = "A") }
                    DemoScreen("Screen B")
                }
            }
        }
    }
}

相关问题