我正在try 执行一些列表操作,并且遇到了当单个项更新时所有项重新组合的问题.

https://prnt.sc/8_OAi1Krn-qg

我的模特们;

data class Person(val id: Int, val name: String, val isSelected: Boolean = false)

@Stable
data class PersonsWrapper(val persons: List<Person>)

我的视图模型和更新函数;

private val initialList = listOf(
    Person(id = 0, name = "Name0"),
    Person(id = 1, name = "Name1"),
    Person(id = 2, name = "Name2"),
    Person(id = 3, name = "Name3"),
    Person(id = 4, name = "Name4"),
    Person(id = 5, name = "Name5"),
    Person(id = 6, name = "Name6"),
)

val list = mutableStateOf(PersonsWrapper(initialList))

fun updateItemSelection(id: Int) {
    val newList = list.value.persons.map {
        if (it.id == id) {
            it.copy(isSelected = !it.isSelected)
        } else {
            it
        }
    }
    list.value = list.value.copy(persons = newList)
}

和我的组合功能;

@Composable
fun ListScreen(personsWrapper: PersonsWrapper, onItemClick: (Int) -> Unit) {
    LazyColumn(
        verticalArrangement = Arrangement.spacedBy(2.dp),
        modifier = Modifier.fillMaxSize()
    ) {
        items(personsWrapper.persons, key = { it.id }) {
            ListItem(item = it, onItemClick = onItemClick)
        }
    }
}

所有模型类在Compose_Reports中似乎都很稳定;

stable class Person {
  stable val id: Int
  stable val name: String
  stable val isSelected: Boolean
  <runtime stability> = Stable
}
stable class PersonsWrapper {
  unstable val persons: List<Person>
}

restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun ListScreen(
  stable personsWrapper: PersonsWrapper
  stable onItemClick: Function1<Int, Unit>
)
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun ListItem(
  stable item: Person
  stable onItemClick: Function1<Int, Unit>
)

当我想要更改列表中单个项目的选定状态时,整个列表将被重新组合.我还try 了使用kotlinx.Collection中的ImmuableList和Persistent List.但问题并没有得到解决.

在列表操作时如何避免不必要的重组?

推荐答案

MuableState使用 struct 相等来工作,它会判断您是否使用新实例更新了state.Value.每次 Select 新项目时,都会在上创建列表的新实例.

您可以使用SnapshotStateList,当您使用新实例添加、删除或更新现有项时,它会触发重组.SnapshotStateList是一个列表,在最坏的情况下,它获得的项的复杂度为O(1),而不是迭代整个列表的O(N).

仅使用muableStateListOf

结果是只有一个条目被重新组合.

enter image description here

您可以使用Snapshot状态列表将您的ViewModel更新为

class MyViewModel : ViewModel() {

    private val initialList = listOf(
        Person(id = 0, name = "Name0"),
        Person(id = 1, name = "Name1"),
        Person(id = 2, name = "Name2"),
        Person(id = 3, name = "Name3"),
        Person(id = 4, name = "Name4"),
        Person(id = 5, name = "Name5"),
        Person(id = 6, name = "Name6"),
    )

    val people = mutableStateListOf<Person>().apply {
        addAll(initialList)
    }

    fun toggleSelection(index: Int) {
        val item = people[index]
        val isSelected = item.isSelected
        people[index] = item.copy(isSelected = !isSelected)
    }
}

ListItem个可组合的

@Composable
private fun ListItem(item: Person, onItemClick: (Int) -> Unit) {
    Column(
        modifier = Modifier.border(3.dp, randomColor())
    ) {
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .clickable {
                    onItemClick(item.id)
                }
                .padding(8.dp)
        ) {
            Text("Index: Name ${item.name}", fontSize = 20.sp)
            if (item.isSelected) {
                Icon(
                    modifier = Modifier
                        .align(Alignment.CenterEnd)
                        .background(Color.Red, CircleShape),
                    imageVector = Icons.Default.Check,
                    contentDescription = "Selected",
                    tint = Color.Green,
                )
            }
        }
    }
}

你的 list

@Composable
fun ListScreen(people: List<Person>, onItemClick: (Int) -> Unit) {
    LazyColumn(
        verticalArrangement = Arrangement.spacedBy(2.dp),
        modifier = Modifier.fillMaxSize()
    ) {

        items(items = people, key = { it.hashCode() }) {

            ListItem(item = it, onItemClick = onItemClick)
        }
    }
}

我用来直观地判断重组的代码

fun randomColor() = Color(
    Random.nextInt(256),
    Random.nextInt(256),
    Random.nextInt(256),
    alpha = 255
)

使用视图状态

结果

enter image description here

sealed class ViewState {
    object Loading : ViewState()
    data class Success(val data: List<Person>) : ViewState()
}

并将视图模型更新为

class MyViewModel : ViewModel() {

    private val initialList = listOf(
        Person(id = 0, name = "Name0"),
        Person(id = 1, name = "Name1"),
        Person(id = 2, name = "Name2"),
        Person(id = 3, name = "Name3"),
        Person(id = 4, name = "Name4"),
        Person(id = 5, name = "Name5"),
        Person(id = 6, name = "Name6"),
    )

    private val people: SnapshotStateList<Person> = mutableStateListOf<Person>()

    var viewState by mutableStateOf<ViewState>(ViewState.Loading)
        private set

    init {
        viewModelScope.launch {
            delay(1000)
            people.addAll(initialList)
            viewState = ViewState.Success(people)
        }
    }

    fun toggleSelection(index: Int) {
        val item = people[index]
        val isSelected = item.isSelected
        people[index] = item.copy(isSelected = !isSelected)
        viewState = ViewState.Success(people)
    }
}

1000毫秒,延迟是为了演示.在真实的应用程序中,你将从REST或数据库获取数据.

使用视图状态显示列表或加载的屏幕

@Composable
fun ListScreen(
    viewModel: MyViewModel,
    onItemClick: (Int) -> Unit
) {

    val state = viewModel.viewState
    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        when (state) {
            is ViewState.Success -> {

                val people = state.data
                LazyColumn(
                    verticalArrangement = Arrangement.spacedBy(2.dp),
                    modifier = Modifier.fillMaxSize()
                ) {
                    items(items = people, key = { it.id }) {
                        ListItem(item = it, onItemClick = onItemClick)
                    }
                }
            }

            else -> {
                CircularProgressIndicator()
            }
        }
    }
}

Android相关问答推荐

打开平板电脑的下载文件夹中的文件,例如使用其mimeType将Intent发送到我们的应用程序

在Android Studio Iguana上运行示例代码时,Gradle Build错误

如何在Android中使用TextView设置动态文本的样式

AdMob:MobileAds. initialize()—java. lang. xml对于某些设备不能强制转换为java. lang. String""

Android Gradle/Groovy,如何将文件复制到APK

BroadCastReceiver的onReceive方法中的Intent上的Extras为空

将DiffUtils用于Android上的Recrecerview适配器

如何在Android中编写挂起函数和stateflow的单元测试

Lateinit变量结果始终以kotlin格式未初始化

在Android中使用Room从SQlite数据库中获取实体列表的正确方式是什么?

学习Kotlin问题.无法理解Modifier参数

Android Studio SQLite 错误:列不正确(没有这样的列:_id)

弹出导航堆栈后,Compose 无法访问 Hilt View Model

ComposeView 抢走了 AndroidTV 内容的焦点

Android Jetpack Compose全宽度抽屉优化

相机2问题:设置AE区域、AF区域和AWB区域.

如何在 react native 应用程序中显示复选框?当它在 android 模拟器中可见时

Gradle 构建错误:找不到 semver4j-0.16.4-nodeps.jar

在 jetpack compose 中使用 .shadow 和 Button 会导致问题

为什么使用 React Native 和 expo 创建的 APK 体积这么大?