Animation on LazyColumn the RecyclerView Equivalent

Axel Briano
4 min readAug 18, 2021

--

It is a common practice while working on the so-called RecyclerView we need to animate an item when the user changes its data. So the context of what the user does is more clear. And the user can take a grasp of the effect of the action that they do. Take an example of deleting the task or note within a list of items, if the item themselves are just ‘snapped’ to thin air, it is sometimes confusing and unpleasant.

So when I google ‘compose animate item changes on lazycolumn’ there isn’t much to read. First, because it is still experimental and second there aren’t many people trying to do it and share it online. Since likely will change in the future. So I’m gonna share the solution that I come up with. Please take a note that at the time I’m writing this the Compose version is still 1.0.0 so maybe the API will likely change because most of this Composable is still experimental.

First, let us define the data class and dependencies that we are gonna use in this example.

...    
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.0-beta02"
...

As we can see there are isVisible properties in the Note data class. This will be the key to our animation later. We also need to define an id for our data class, so the state for each item will be applied correctly. Second, is the main screen to hold our list.

As I said before, we need the id of our model which is Note to define the key for lazycolumn. And wrap our ItemNote composable with AnimatedVisibility to animate our item. The composable take visible param to determine when to launch our animation which we pass isVisible prop from the data class. We only need pass exit since we are just gonna remove the item from our list no need for enter. For a more complete explanation to define the animation on jetpack compose just hit its official docs Animation | Jetpack Compose | Android Developers. We also need to pass callback function to each of ItemNote composable to change its own isVisible props. And lastly, define ItemNote composable.

For the swipeable effect just hit its official docs Gestures | Jetpack Compose | Android Developers because I barely made any changes from the snippet there. We use boxes here because we need to draw a white background which is our note content with text over the red one that has only one delete button at the end.

So lets first define the most outer box

Box(
modifier = Modifier
.fillMaxWidth()
.height(75.dp)
.swipeable(
state = swipeableState,
anchors = anchors,
thresholds = { _, _ -> FractionalThreshold(0.5f) },
orientation = Orientation.Horizontal
)
.background(Color(0xFFDA5D5D))
){
...
}

So this outer box with some red background will be controlling the swipeableState that we define first. After that, we will draw our delete button at the end when we swipe the note content to the left.

...
// this box will go inside BoxScope above
Box
(
modifier = Modifier
.fillMaxHeight()
.align(Alignment.CenterEnd)
.padding(end = 10.dp)
) {
IconButton(
modifier = Modifier.align(Alignment.Center),
onClick = {
coroutineScope.launch {
onDeleteNote(note)
}
}
) {
Icon(
Icons.Default.Delete,
contentDescription = "Delete this note",
tint = Color.White
)
}
}
...

We will dispatch the callback within coroutine, the other thing is pretty self-explanatory. Next, still in the inside of the red background box, we define another AnimatedVisibility to animate the content within itself. One thing to note here for the animationSpec, this animation will launch first which is sliding far to the left until the white background is not visible to user. After that, the fade animation for the red background will launch to fully remove the note container. So you need to match the duration of sliding out animation and the delay value for fade out for its animation to run seamlessly.

Box(...) {
...
Box(...) {
...
}
AnimatedVisibility(
visible = note.isVisible,
exit = slideOutHorizontally(
targetOffsetX = { -it },
animationSpec = TweenSpec(200, 0, FastOutLinearInEasing)
)
) {
...
}
}

Lastly is composable for the white background. In this case, I’m gonna use ConstraintLayout because of several reasons. For a more detailed explanation, again just hit its official docs ConstraintLayout in Compose | Jetpack Compose | Android Developers.

// this ConstraintLayout will go inside AnimatedVisibility
ConstraintLayout(
modifier = Modifier
.offset { IntOffset(swipeableState.offset.value.roundToInt(), 0) }
.fillMaxHeight()
.fillMaxWidth()
.background(Color.White)
.clickable { }
) {
val (titleText, contentText, divider) = createRefs()

Text(
modifier = Modifier.constrainAs(titleText) {
top.linkTo(parent.top, margin = 12.dp)
start.linkTo(parent.start, margin = 18.dp)
end.linkTo(parent.end, margin = 18.dp)
width = Dimension.fillToConstraints
},
text = note.title,
fontSize = 16.sp,
fontWeight = FontWeight(500),
textAlign = TextAlign.Start
)

Text(
modifier = Modifier.constrainAs(contentText) {
bottom.linkTo(parent.bottom, margin = 12.dp)
start.linkTo(parent.start, margin = 18.dp)
end.linkTo(parent.end, margin = 18.dp)
width = Dimension.fillToConstraints
},
text = note.content,
textAlign = TextAlign.Start
)

Divider(
modifier = Modifier.constrainAs(divider) {
start.linkTo(parent.start)
end.linkTo(parent.end)
bottom.linkTo(parent.bottom)
width = Dimension.fillToConstraints
},
thickness = 1.dp,
color = Color.DarkGray
)
}

Just a simple two line’s of text and divider at the very bottom. In this layout, we are gonna set the offset of ConstraintLayout with modifier to match the swipeableState offset value. So it creates a moving white background animation like this.

As for the complete code for the Composable Item

And so that’s it for creating slidingOut + fadeOut animation on lazyColumn in the new Jetpack Compose API. Hope this solution that I’ve come with will help you find a reference to create your own animation implementation.

--

--

Axel Briano
Axel Briano

Responses (1)