android Jetpack Compose惰性列在单个项更新时重新组合所有项

w1jd8yoj  于 2022-12-09  发布在  Android
关注(0)|答案(2)|浏览(302)

我试图做一些列表操作,我遇到了问题的所有项目重组时,一个单一的项目更新。
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>)

我的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 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)
}

和My可组合函数;

@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中的所有模型类看起来都很稳定;
第一个
当我想改变列表中单个项目的选中状态时,整个列表会被重新组合。我也尝试过使用kotlinx. collections中的ImmutableList和Persistant列表。但是问题没有解决。
列表操作时如何避免不必要的重组?

inn6fuwd

inn6fuwd1#

MutableState使用结构相等来检查您是否使用新示例更新了state.value。每次您选择一个新项目时,您都会创建一个新的列表示例。
如果你在一个列表中添加、删除或更新一个条目,那么你可以使用SnapshotStateList来触发重新组合。SnapshotStateList是一个列表,它可以用时间复杂度O(1)来获得条目,而不是在最坏的情况下用O(n)来迭代整个列表。

仅使用可变状态列表

结果是只有单个项目得到重组。

您可以将ViewModel with SnapshotState列表更新为

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

您的清单

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

使用视图状态

测试结果

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

并将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"),
    )

    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 ms和延迟是为了演示。在真实的的应用程序中,你将从REST或DB中获得数据。
显示列表或使用ViewState加载的屏幕

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

r3i60tvu2#

我认为这是因为你使用.map {..},它创建了一个新的完整的列表集。
返回一个列表,其中包含将给定的转换函数应用于原始集合中每个元素的结果。

public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
    return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}

通过这样做

list.value = list.value.copy(persons = newList)

您实际上是在创建一组全新列表,并将它们分配给LazyColumn,从而使其完全重新组合。
我建议如下:
使用SnapshotStateListmutableStateListOf

private val initialList = mutableStateListOf(
        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"),
    )

只需使用列表迭代器修改结构(在您的情况下)

fun updateItemSelection(id: Int) {

        val iterator = list.value.persons.listIterator()

        while (iterator.hasNext()) {
            val current = iterator.next()
            if (current.id == id) {
                iterator.set(current.copy(isSelected = !current.isSelected))
            }
        }

      //  list.value = list.value.copy(persons = newList) // <- you don't need to assign a new list here as well. Remove this line
    }

相关问题