首页 > 解决方案 > 滚动时 LazyColumn 不保持项目的状态

问题描述

遵循Pathway谷歌关于 Jetpack compose 的代码实验室,我正在尝试这段代码

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

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

提前致谢!

标签: androidandroid-jetpack-compose

解决方案


[更新]

使用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).

现在的代码更优雅更短,我们甚至不需要提升状态,它可以保持在内部。我现在唯一不太确定的事情是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。在这种情况下,要解决此问题,只需将状态提升到消费者的范围LazyColumn

另外我们需要在内部使用lambdamutableStateMapOf()代替,否则 Compose-core 引擎不会察觉到这种变化。MutableMapOfremember { ... }

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

@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)
        }
    }
}

快乐作曲!


推荐阅读