kotlin 防止在Jetpack Compose中对列表更新进行不必要的重组

vfh0ocws  于 2023-08-06  发布在  Kotlin
关注(0)|答案(2)|浏览(175)

我正在使用Jetpack Compose编写Android应用程序。我有一个名为MultiSelectGroup的Composable,每当单击FilterChip时,它需要修改并返回一个选择列表。代码如下:

@OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class)
@Composable
fun MultiSelectGroup(
    items: List<String>,
    currentSelections: List<String>,
    onSelectionsChanged: (List<String>) -> Unit,
) {
    FlowRow {
        items.forEach { item ->
            FilterChip(
                label = { Text(item) },
                selected = currentSelections.contains(item),
                onClick = {
                    val newSelectedChips = currentSelections.toMutableList().apply {
                        if (contains(item)) {
                            remove(item)
                        } else {
                            add(item)
                        }
                    }
                    onSelectionsChanged(newSelectedChips)
                },
            )
        }
    }
}

字符串
当前使用以下代码调用此组件:

val allItems = remember { (1..6).map {"$it"} }

val selectedItems = remember {
    mutableStateOf(emptyList<String>())
}

MultiSelectGroup(
    items = allItems,
    currentSelections = selectedItems,
    onSelectionsChanged = { selectedItems.value = it },
)


问题是,这种方法在重组方面似乎相当低效;每次单击FilterChip时,所有FilterChip都将重新组合,无论它们是否在视觉上发生变化。我目前的理解是,这是因为列表正在重新设置,并且List是一种更不稳定的数据类型,Compose决定重新呈现依赖于List的所有组件,而不仅仅是List中的元素发生了变化。
我已经考虑过将每个FilterChiponClick中的“更新列表”逻辑从组件中提升出来--然而,这个组件执行这个逻辑似乎是明智的,因为这个行为总是相同的,并且只会在每次使用MultiSelectGroup时重复。我也尝试过使用mutableStateListkeyderivedStatedOf的组合,但我还没有找到一个有效的解决方案。
像这样的组件注定要重新组合它的每个子组件吗?或者有没有一种方法可以优化这种视图的重组?提前感谢!

y0u0uwnf

y0u0uwnf1#

您当前的函数是不稳定的,因为你提到的参数

restartable scheme("[androidx.compose.ui.UiComposable]") fun MultiSelectGroup(
  unstable items: List<String>
  unstable currentSelections: List<String>
  stable onSelectionsChanged: Function1<List<String>, Unit>
)

字符串
您可以创建一个包含文本和项选择状态的类,而不是两个列表。

data class Item(val text: String, val selected: Boolean = false)


您可以将List替换为稳定的SnapshotStateList

@OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class)
@Composable
fun MultiSelectGroup(
    items: SnapshotStateList<Item>,
    onSelectionsChanged: (index: Int, item: Item) -> Unit,
) {

    SideEffect {
        println("MultiSelectGroup scope recomposing...")
    }

    FlowRow {

        SideEffect {
            println("FlowRow scope recomposing...")
        }
        
        items.forEachIndexed { index, item ->
            FilterChip(
                label = { Text(item.text) },
                selected = item.selected,
                onClick = {
                    onSelectionsChanged(index, item.copy(selected = item.selected.not()))
                },
            )
        }
    }
}


并将其用作

@Preview
@Composable
private fun Test() {
    val allItems = remember { (1..6).map { Item(text = "$it") } }

    val selectedItems = remember {
        mutableStateListOf<Item>().apply {
            addAll(allItems)
        }
    }

    MultiSelectGroup(
        items = selectedItems,
        onSelectionsChanged = { index: Int, item: Item ->
            selectedItems[index] = item
        }
    )
}


会的

restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun MultiSelectGroup(
  stable items: SnapshotStateList<Item>
  stable onSelectionsChanged: Function2<@[ParameterName(name = 'index')] Int, @[ParameterName(name = 'item')] Item, Unit>
)


实际上,您可以发送一个列表而不是SnapshotList,但随后FlowRow范围将被重新组合。使用SnapshotStateList,两个作用域都是不可跳过的。
你也可以不使用回调函数,因为你可以在MultiSelectGroup中更新SnapshotStateList,但我不想在函数中更新输入的属性。

h5qlskok

h5qlskok2#

Google已经给出了一套最佳实践,你可以遵循,以避免不必要的重组。
https://developer.android.com/jetpack/compose/performance/bestpractices

相关问题