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

时间:2021-03-13 10:47:58

标签: android-jetpack-compose android-jetpack-navigation

如果我们为列表项创建状态,如 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)
        )
    }
}

2 个答案:

答案 0 :(得分:5)

使用 rememberSaveable 可以解决问题。

感谢https://stackoverflow.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()
        }
    }

答案 1 :(得分:3)

<块引用>

但是当我们展开一个项目时,点击按钮,转到详细信息屏幕,然后返回到我们松开展开状态的项目。

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

<块引用>

保存项目状态的最佳方法是什么?

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

或者,将状态存储在作用域为您的 Activity 或至少该根组合的视图模型中。

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