繁体   English   中英

滚动时,LazyColumn 不保留项目的 state

[英]LazyColumn is not keeping the state of items when scrolling

遵循 Google 关于 Jetpack compose 的Pathway代码实验室,我正在尝试此代码

import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Divider
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.codelab.basics.ui.BasicsCodelabTheme

class MainActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           MyApp {
               MyScreenContent()
           }
       }
   }
}

@Composable
fun MyApp(content: @Composable () -> Unit) {
   BasicsCodelabTheme {
       Surface(color = Color.Yellow) {
           content()
       }
   }
}

@Composable
fun MyScreenContent(names: List<String> = List(1000) { "Hello Android #$it" }) {
   val counterState = remember { mutableStateOf(0) }

   Column(modifier = Modifier.fillMaxHeight()) {
       NameList(names, Modifier.weight(1f))
       Counter(
           count = counterState.value,
           updateCount = { newCount ->
               counterState.value = newCount
           }
       )
   }
}

@Composable
fun NameList(names: List<String>, modifier: Modifier = Modifier) {
   LazyColumn(modifier = modifier) {
       items(items = names) { name ->
           Greeting(name = name)
           Divider(color = Color.Black)
       }
   }
}

@Composable
fun Greeting(name: String) {
   var isSelected by remember { mutableStateOf(false) }
   val backgroundColor by animateColorAsState(if (isSelected) Color.Red else Color.Transparent)

   Text(
       text = "Hello $name!",
       modifier = Modifier
           .padding(24.dp)
           .background(color = backgroundColor)
           .clickable(onClick = { isSelected = !isSelected })
   )
}

@Composable
fun Counter(count: Int, updateCount: (Int) -> Unit) {
   Button(
       onClick = { updateCount(count + 1) },
       colors = ButtonDefaults.buttonColors(
           backgroundColor = if (count > 5) Color.Green else Color.White
       )
   ) {
       Text("I've been clicked $count times")
   }
}

@Preview("MyScreen preview")
@Composable
fun DefaultPreview() {
   MyApp {
       MyScreenContent()
   }
}

我注意到,只要项目在屏幕上可见(预期行为), LazyColumn就会重新组合项目,但是, Greeting小部件的本地 state 完全丢失了!

我相信这是 Compose 中的一个错误,理想情况下,作曲家应该考虑remember缓存的 state。 有没有一种优雅的方法来解决这个问题?

提前致谢!

[更新]

使用rememberSaveable {...}可以在 android 配置更改和scrollability性更改中存活下来

文档

Remember the value produced by init.
It behaves similarly to remember, but the stored value will survive the activity or process recreation using the saved instance state mechanism (for example it happens when the screen is rotated in the Android application).

现在的代码更优雅更短,我们甚至不需要吊起state,它可以保留在内部。 我现在唯一不太确定使用rememberSaveable是当列表变得越来越大时是否会有任何性能损失,比如1000 个项目。

@Composable
fun Greeting(name: String) {
    val isSelected = rememberSaveable { mutableStateOf(false) }
    val backgroundColor by animateColorAsState(if (isSelected.value) Color.Red else Color.Transparent)

    Text(
        text = "Hello $name! selected: ${isSelected.value}",
        modifier = Modifier
            .padding(24.dp)
            .background(color = backgroundColor)
            .clickable(onClick = {
                isSelected.value = !isSelected.value
            })
    )
}

[原答案]

根据@CommonWare的回答LazyColumn将在屏幕外处理可组合项及其状态,这意味着当LazyColumn再次重新组合Compsoables时,它将重新开始state 要解决此问题,只需将 state提升到消费者的LazyColumn ,在本例中为 LazyColumn。

此外,我们需要在remember {... } lambda 中使用mutableStateMapOf()而不是MutableMapOf ,否则 Compose-core 引擎将不会注意到此更改。

到目前为止,这里是代码:

@Composable
fun NameList(names: List<String>, modifier: Modifier = Modifier) {

    val selectedStates = remember {
        mutableStateMapOf<Int, Boolean>().apply {
            names.mapIndexed { index, _ ->
                index to false
            }.toMap().also {
                putAll(it)
            }
        }
    }

    LazyColumn(modifier = modifier) {
        itemsIndexed(items = names) { index, name ->
            Greeting(
                name = name,
                isSelected = selectedStates[index] == true,
                onSelected = {
                    selectedStates[index] = !it
                }
            )
            Divider(color = Color.Black)
        }
    }
}

快乐作曲!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM