Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can we animate addition and removal of items in a lazy list (e.g. LazyColumn)?

I had been struggling trying to get animateItemPlacement() to work at all in my application, and thought it was completely broken until I found other people's examples that seemed to work.

I've finally figured out how my app is different to the other examples:

  • Adding an item to the list: doesn't animate, but I'd like it to
  • Removing an item from the list: doesn't animate, but I'd like it to
  • Shuffling all the existing items in the list: animates fine today

In the application I'm working on, shuffling never happens, so it took me a while to identify that shuffling did in fact work while basic addition and removals of items did not.

Self-contained example app:

(Sorry that it's Desktop only atm, that's because it's using UUID for the ID generation and I don't have a good cross-platform solution to that yet. I wish Kotlin would add UUID to core library... you can probably make a small adjustment to it to get it to work on Android too, but I think Desktop is the simplest platform to get an example to run anyway.)

import androidx.compose.animation.core.EaseInOutCubic
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.singleWindowApplication
import kotlinx.coroutines.delay
import java.util.UUID

fun main() = singleWindowApplication {
    MaterialTheme(colorScheme = darkColorScheme()) {
        Surface(modifier = Modifier.fillMaxSize()) {
            ItemPlacementAnimationMockup()
        }
    }
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ItemPlacementAnimationMockup() {
    val history = remember { mutableStateListOf<HistoryItem>() }

    // Some simulated activity
    LaunchedEffect(Unit) {
        repeat(4) {
            history.add(HistoryItem())
        }

        while (true) {
            val newItem = HistoryItem()
            delay(timeMillis = 1000)
            history.add(newItem)
            delay(timeMillis = 1000)
            history.remove(newItem)
            delay(timeMillis = 1000)
            history.shuffle()
        }
    }

    LazyColumn(
        modifier = Modifier
            .padding(16.dp)
            // Uncomment the enxt line to align at the bottom instead
            // .wrapContentHeight(Alignment.Bottom)
    ) {
        items(history, key = { it }) {
            Text("Item $it", Modifier.animateItemPlacement(animationSpec = tween(
                durationMillis = 800,
                easing = EaseInOutCubic,
            )))
        }
    }
}

private data class HistoryItem(
    val id: UUID = UUID.randomUUID(),
)

Is there any way to make animateItemPlacement() work for all three scenarios?

I know I can use animateContentSize() to sort of simulate animating addition and removal, but it really is just simulation, and doesn't work for the case where you add one item to the list and remove some other item at the same time. In that case, the content size doesn't change, so no animation happens at all.

like image 369
Hakanai Avatar asked Oct 23 '25 15:10

Hakanai


1 Answers

Another option to try is the animateItem Modifier, which will animate insertion, deletion and reordering of items.

You can use it as follows:

LazyColumn {
    items(
        items,
        key = { it }  // key is required for correct animations
    ) { item ->
        Text(
            modifier = Modifier.animateItem(),  // apply Modifier
            text = item.text
        )
    }
}
like image 127
Sam Avatar answered Oct 26 '25 09:10

Sam



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!