首页 > 解决方案 > 如何在 Compose 中保留列控件的展开项?

问题描述

代码 A 来自此处的官方示例代码。

作者告诉我以下内容:

如果您展开第 1 项,滚动到第 20 号并返回到 1,您会注意到 1 又回到了原始大小。

问题1:为什么用代码A向前滚动后向后滚动项目后展开项目会恢复到原来的大小?

问题2:如何在向前滚动然后向后滚动后保持展开项目?

代码 A

@Composable
private fun Greetings(names: List<String> = List(1000) { "$it" } ) {
    LazyColumn(modifier = Modifier.padding(vertical = 4.dp)) {
        items(items = names) { name ->
            Greeting(name = name)
        }
    }
}

@Composable
private fun Greeting(name: String) {    
    var expanded by remember { mutableStateOf(false) }    
    val extraPadding by animateDpAsState(                
        if (expanded) 48.dp else 0.dp
    ) 

    Surface(
        color = MaterialTheme.colors.primary,
        modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
    ) {
        Row(modifier = Modifier.padding(24.dp)) {
            Column(modifier = Modifier
                .weight(1f)
                .padding(bottom = extraPadding)
            ) {
                Text(text = "Hello, ")
                Text(text = name)
            }
            OutlinedButton(
                onClick = { expanded = !expanded }
            ) {
                Text(if (expanded) "Show less" else "Show more")
            }

        }
    }
}

添加内容

问题 3:如果我使用代码 B,我发现在我向前滚动然后向后滚动后可以保留展开的项目。为什么 ?

代码 B

@Composable
private fun Greetings(names: List<String> = List(50) { "$it" } ) {
    Column(
        modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())
    )
    {
        for (name in names) {
            Greeting(name = name)
        }
    }
}

... 

再次:

我根据jVCODE的建议重写了代码C,它可以工作,在我向前滚动后可以保留展开项目,然后在我替换为后退项目后var expanded by remember { mutableStateOf(false) }保留var expanded by rememberSaveable { mutableStateOf(false) }

根据 Arpit Shukla 的观点:

当您在 LazyColumn 中滚动时,不再可见的可组合项将从组合树中删除,当您滚动回它们时,它们会从头开始重新组合。这就是expanded再次初始化为false的原因。

在我看来,rememberSaveable只有在我旋转屏幕时才可用。

所以我认为var expanded by rememberSaveable { mutableStateOf(false) }当我向前滚动然后向后滚动项目时会重新启动并分配为 false,并且扩展项目将恢复到原始大小。但实际上,在我向前滚动然后向后滚动项目之后,可以保留展开项目。

问题4:为什么rememberSaveable在这种情况下可以很好地工作?

代码 C

  @Composable
    private fun Greetings(names: List<String> = List(1000) { "$it" } ) {
        LazyColumn(modifier = Modifier.padding(vertical = 4.dp)) {
            items(items = names) { name ->
                Greeting(name = name)
            }
        }
    }

    @Composable
    private fun Greeting(name: String) {    
      var expanded by rememberSaveable { mutableStateOf(false) }
       ...
    }

标签: kotlinandroid-jetpack-compose

解决方案


1- 当 Composable 进入组合时,remember 中的值被初始化。使用 LazyColumn 当组合物不可见时从组合物三中移除不可见并且当您再次滚动回同一项目时,remember{..}初始化值。

在此处输入图像描述

2-您可以创建模型类的 UI 版本和数据类的扩展属性。你可以

data class UiModel(
    ...
    var expanded: Boolean = false
)

这样您就可以在 ViewModel 中存储扩展状态。

    LazyColumn(

        content = {

            items(tutorialList) { item: UiModel ->

                var isExpanded by remember(key1 = item.title) { mutableStateOf(item.expanded) }

                UiModel(
                    model = item,
                    onExpandClicked = {
                        item.expanded = !item.expanded
                        isExpanded = item.expanded
                    },
                    expanded = isExpanded
                )
            }
        }
    )

结果是

在此处输入图像描述

编辑

LazyColumnLazyRow使用名为SubComposeLayout的布局,您可以在此处阅读 Google 开发人员的详细答案,它会像 RecyclerView 一样延迟加载。Compose Runtime 开发人员 Leland Richardson 撰写的一篇好文章Under the Jetpack Compose是一个很好的资源,可以帮助您了解幕后发生的事情。

当从组合中移除的组合依赖于架构时,例如简单的加载和显示结果在一个函数中,例如

@Composable fun App() {
 val result = getData()
 if (result == null) {
   Loading(...)
 } else {
   Header(result)
   Body(result)
 }
}

实际上编译为

fun App($composer: Composer) {
 val result = getData()
 if (result == null) {
   $composer.start(123)
   Loading(...)
   $composer.end()
 } else {
   $composer.start(456)
   Header(result)
   Body(result)
   $composer.end()
 }
}

您可以从媒体文章中查看有关共享的媒体链接中的片段的组成的其他详细信息。

跟踪 Composable 何时进入和退出组合的一种简单方法是使用DisposableEffect函数

它代表了组合生命周期的副作用。

  • 第一次触发(当可组合进入组合时),然后每次其键更改时触发。
  • 最后需要 onDispose 回调。它在可组合项离开组合时被处理,并且在其键发生变化时也在每次重新组合时被处理。在这种情况下,效果会被处理并重新启动。

推荐阅读