在 Jetpack Compose 中导航时记住列表项状态

Remember list item states while navigation in Jetpack Compose

如果我们为列表项创建状态,如 val state = remember(it) { mutableStateOf(ItemState()) },那么我们在滚动时会释放展开状态。

如果我们在 LazyColumn 之前提升生成更高的状态,那么扩展状态会正确保存

 val states = items.map { remember(it) { mutableStateOf(ItemState()) } }
 LazyColumn(modifier = Modifier.fillMaxSize()) {....

但是当我们展开一个项目时,单击按钮,转到详细信息屏幕,然后返回到我们松开展开状态的项目。 保存项目状态的最佳方式是什么?

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            RememberStateTheme {
                val navController = rememberNavController()

                NavHost(navController, startDestination = "items") {
                    composable("items") {
                        Greeting(
                            onItemClick = { navController.navigate("details/$it") }
                        )
                    }
                    composable(
                        "details/{index}",
                        arguments = listOf(navArgument("index") { type = NavType.IntType })
                    ) { backStackEntry ->
                        DetailsScreen(backStackEntry.arguments?.getInt("index") ?: -1)
                    }
                }
            }
        }
    }
}

@Composable
fun Greeting(
    onItemClick: (Int) -> Unit
) {
    val items = remember { (0..100).toList() }

    Surface(color = MaterialTheme.colors.background) {
        val states = items.map { remember(it) { mutableStateOf(ItemState()) } }

        LazyColumn(modifier = Modifier.fillMaxSize()) {
            items(items) { item ->
                // If we create state here like val state = remember(it) { mutableStateOf(ItemState()) }
                // then we loose expanded state while scrolling
                // If we lift up states generating higher before LazyColumn then expand state
                // is saving properly
                //
                // But when we expand an item, click the button and then go back to items we loose
                // expanded state
                val state = states[item]

                key(item) {
                    Item(index = item,
                        state = state.value,
                        onClick = { onItemClick(item) },
                        modifier = Modifier
                            .fillMaxSize()
                            .clickable {
                                state.value.changeState()
                            }
                    )
                }
                Divider()
            }
        }
    }
}

@Composable
fun Item(
    index: Int,
    state: ItemState,
    modifier: Modifier = Modifier,
    onClick: () -> Unit
) {
    Box(modifier = modifier) {
        Column(
            horizontalAlignment = Alignment.CenterHorizontally,
            modifier = Modifier.align(Alignment.Center)
        ) {
            Text(
                text = index.toString(),
                modifier = Modifier.padding(16.dp)

            )

            if (state.expanded) {
                Button(
                    onClick = onClick,
                    modifier = Modifier.padding(8.dp)
                ) {
                    Text(text = "Click me")
                }
            }
        }
    }
}

class ItemState {

    val expanded: Boolean
        get() = _expanded.value

    private val _expanded = mutableStateOf(false)

    fun changeState() {
        _expanded.value = !_expanded.value
    }
}

@Composable
fun DetailsScreen(
    index: Int,
    modifier: Modifier = Modifier
) {
    Box(
        modifier = modifier
            .fillMaxSize()
            .background(Color.Gray.copy(alpha = 0.3f))
    ) {
        Text(
            text = index.toString(),
            modifier = Modifier.align(Alignment.Center)
        )
    }
}

But when we expand an item, click the button, go to details screen and then go back to items we loose expanded state.

正确。 remember() 记住 特定组合的范围 。这意味着它会记住跨重组,但不会记住当组合被单独的组合替换时。在您的情况下,导航将您的 Greeting() 组合替换为 DetailsScreen() 组合。

What is the best way to save items state?

将状态进一步提升到不会被导航取代的组合。在这种情况下,那将是您的根组合,您可以在其中进行 rememberNavController() 调用。

或者,将状态存储在范围为您的 activity 或至少为该根组合的视图模型中。

如果你想让这个状态在你的进程生命周期之外持续存在,我认为我们应该使用效果通过存储库将状态保存到某个持久存储(例如,JSON文件)并从该存储中恢复状态。但是,我还没有尝试过这种方法。

使用rememberSaveable解决问题。

感谢https://whosebug.com/users/1424349/leland-richardson

    LazyColumn(modifier = Modifier.fillMaxSize()) {
        items(items) { item ->
            val state = rememberSaveable(item) { mutableStateOf(ItemState()) }
    
            key(item) {
                Item(index = item,
                    state = state.value,
                    onClick = { onItemClick(item) },
                    modifier = Modifier
                        .fillMaxSize()
                        .clickable {
                            state.value.changeState()
                        }
                )
            }
            Divider()
        }
    }