I use Jetpack Compose UI to build a simple TODO app. The idea is to have a list of tasks that could be checked or unchecked, and checked tasks should go to the end of the list.
Everything is working fine except when I check the first visible item on the screen it moves down along with the scroll position.
I believe that has something to do with LazyListState
, there is such function:
/**
* When the user provided custom keys for the items we can try to detect when there were
* items added or removed before our current first visible item and keep this item
* as the first visible one even given that its index has been changed.
*/
internal fun updateScrollPositionIfTheFirstItemWasMoved(itemsProvider: LazyListItemsProvider) {
scrollPosition.updateScrollPositionIfTheFirstItemWasMoved(itemsProvider)
}
So I would like to disable this kind of behavior but I didn't find a way.
Below is a simple code to reproduce the problem and a screencast. The problem in this screencast appears when I try to check "Item 0", "Item 1" and "Item 4", but it works as I expect when checking "Item 7" and "Item 8".
It also behaves the same if you check or uncheck any item that is currently the first visible item, not only if the item is first in the whole list.

class MainActivity : ComponentActivity() {
@OptIn(ExperimentalFoundationApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
val checkItems by remember { mutableStateOf(generate(20)) }
LazyColumn() {
items(
items = checkItems.sortedBy { it.checked.value },
key = { item -> item.id }
) { entry ->
Row(
modifier = Modifier.animateItemPlacement(),
verticalAlignment = Alignment.CenterVertically,
) {
Checkbox(
checked = entry.checked.value,
onCheckedChange = {
checkItems.find { it == entry }
?.apply { this.checked.value = !this.checked.value }
}
)
Text(text = entry.text)
}
}
}
}
}
}
}
}
data class CheckItem(val id: String, var checked: MutableState<Boolean> = mutableStateOf(false), var text: String = "")
fun generate(count: Int): List<CheckItem> =
(0..count).map { CheckItem(it.toString(), mutableStateOf(it % 2 == 0), "Item $it") }