@OptIn(ExperimentalFoundationApi::class) @Composable private fun AnimatedList() { Column(modifier = Modifier.fillMaxSize()) { val items: SnapshotStateList = remember { mutableStateListOf().apply { repeat(20) { add(MyData(uuid = UUID.randomUUID().toString(), "Row $it")) } } } val lazyListState = rememberLazyListState() LazyColumn( modifier = Modifier .fillMaxWidth() .weight(1f), state = lazyListState, contentPadding = PaddingValues(horizontal = 10.dp, vertical = 0.dp), verticalArrangement = Arrangement.spacedBy(4.dp) ) { items( items = items, key = { it.uuid } ) { Row( modifier = Modifier .animateItemPlacement( tween(durationMillis = 200) ) .shadow(1.dp, RoundedCornerShape(8.dp)) .background(Color.White) .fillMaxWidth() .padding(8.dp), verticalAlignment = Alignment.CenterVertically ) { Image( modifier = Modifier .clip(RoundedCornerShape(10.dp)) .size(50.dp), painter = painterResource(id = R.drawable.landscape1), contentScale = ContentScale.FillBounds, contentDescription = null ) Spacer(modifier = Modifier.width(10.dp)) Text(it.value, fontSize = 18.sp) } } } var fromString by remember { mutableStateOf("7") } var toString by remember { mutableStateOf("3") } var animate by remember { mutableStateOf(false) } val coroutineScope = rememberCoroutineScope() val animatable = remember { Animatable(0, IntToVector) } if (animate) { val from = try { Integer.parseInt(fromString) } catch (e: Exception) { 0 } val to = try { Integer.parseInt(toString) } catch (e: Exception) { 0 } animatedSwap( lazyListState = lazyListState, items = items, from = from, to = to ) { animate = false } } Row(modifier = Modifier.fillMaxWidth()) { TextField( value = fromString, onValueChange = { fromString = it }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number) ) TextField( value = toString, onValueChange = { toString = it }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number) ) } Button( modifier = Modifier .padding(8.dp) .fillMaxWidth(), onClick = { animate = true // alternativeAnimate(fromString, toString, coroutineScope, animatable, items) } ) { Text("Swap") } } } private fun alternativeAnimate( fromString: String, toString: String, coroutineScope: CoroutineScope, animatable: Animatable, items: SnapshotStateList ) { val from = try { Integer.parseInt(fromString) } catch (e: Exception) { 0 } val to = try { Integer.parseInt(toString) } catch (e: Exception) { 0 } val difference = from - to val increasing = difference < 0 var currentValue: Int = from coroutineScope.launch { animatable.snapTo(from) animatable.animateTo(to, tween(350 * abs(difference)), block = { val nextValue = this.value if (abs(currentValue - nextValue) == 1) { println("🔥difference: $difference, from: $from, to: $to, Animating current: $currentValue, nextValue: $nextValue") swap(items, currentValue, nextValue) currentValue = nextValue } } ) } } @Composable private fun animatedSwap( lazyListState: LazyListState, items: SnapshotStateList, from: Int, to: Int, onFinish: () -> Unit ) { LaunchedEffect(key1 = Unit) { val difference = from - to val increasing = difference < 0 var currentValue: Int = from repeat(abs(difference)) { val temp = currentValue if (increasing) { currentValue++ } else { currentValue-- } swap(items, temp, currentValue) // if (!increasing && currentValue == 0) { // lazyListState.animateScrollToItem(0) // } delay(350) } onFinish() } } private fun swap(list: SnapshotStateList, from: Int, to: Int) { val size = list.size if (from in 0 until size && to in 0 until size) { val temp = list[from] list[from] = list[to] list[to] = temp } } class MyData(val uuid: String, val value: String) val IntToVector: TwoWayConverter = TwoWayConverter({ AnimationVector1D(it.toFloat()) }, { it.value.toInt() })