How to create Expandable List in Jetpack Compose — Part 2 — Animations

Mrugendra Thatte
3 min readJun 25, 2023

--

In last post we saw how easy it is to create Expandable lists in Jetpack Compose. If you haven’t read that part yet, here is the Part 1.

Now that we have created an expandable list to show our data in expandable sections, it’s time to make it beautiful.

Here is the code we ended up with for ExpandableList .

@Composable
fun ExpandableList(sections: List<SectionData>) {
val isExpandedMap = rememberSavableSnapshotStateMap {
List(sections.size) { index: Int -> index to true }
.toMutableStateMap()
}

LazyColumn(
content = {
sections.onEachIndexed { index, sectionData ->
Section(
sectionData = sectionData,
isExpanded = isExpandedMap[index] ?: true,
onHeaderClick = {
isExpandedMap[index] = !(isExpandedMap[index] ?: true)
}
)
}
}
)
}

fun LazyListScope.Section(
sectionData: SectionData,
isExpanded: Boolean,
onHeaderClick: () -> Unit
) {

item {
SectionHeader(
text = sectionData.headerText,
isExpanded = isExpanded,
onHeaderClicked = onHeaderClick
)
}

if(isExpanded) {
items(sectionData.items) {
SectionItem(text = it)
}
}
}

@Composable
fun SectionHeader(text: String, isExpanded: Boolean, onHeaderClicked: () -> Unit) {
// Refer last post for section header code
}

@Composable
fun SectionItem(text: String) {
// Refer last post for section item code
}

Adding expanding / collapsing animation for Section Items is very easy.
In LazyListScope.Section() we have if(isExpanded){} section to know if we want to show section items or not.

So lets try to use Jetpack Compose’s AnimatedVisibility(isVisible){} instead of if(){} .

AnimatedVisibility(visible = isExpanded) {
items(sectionData.items) {
SectionItem(text = it)
}
}

But this would not work and gives out syntax error, because AnimatedVisibility is Composable and LazyListScope.Section() is not composable function, but items { /*composable code*/ } is. So lets move things around.

itemsIndexed(sectionData.items) { index, item ->
AnimatedVisibility(visible = isExpanded) {
SectionItem(text = item)
}
}

This make our expansion and collapsing of section items animated. But default animation for AnimatedVisibility is shrink and expand. Where as to create the expansion and collapsing of the whole section we need vertical expand and collapse animation. Jetpack Compose gives us easy way to create these animations with built in functions for expandVertically() and shrinkVertically()

itemsIndexed(sectionData.items) { index, item -> 
AnimatedVisibility(
visible = isExpanded,
enter = fadeIn(animationSpec = tween(ANIMATION_DURATION)) +
expandVertically(animationSpec = tween(ANIMATION_DURATION)),
exit = fadeOut(animationSpec = tween(ANIMATION_DURATION)) +
shrinkVertically(animationSpec = tween(ANIMATION_DURATION))
){
SectionItem(text = item)
}
}

Let’s try our code

Animation demo

That’s great! we can also add similar animation to chevron to complete the animation.

Lets replace our static icon code in SectionHeader with rotation animation

@Composable
fun SectionHeader(text: String, isExpanded: Boolean, onHeaderClicked: () -> Unit) {
Row(modifier = Modifier
.clickable { onHeaderClicked() }
.background(Color.LightGray)
.padding(vertical = 8.dp, horizontal = 16.dp)) {
Text(
text = text,
style = MaterialTheme.typography.h6,
modifier = Modifier.weight(1.0f)
)

val rotation by animateFloatAsState(
targetValue = if (isExpanded) 0f else -180f,
animationSpec = tween(500)
)

Icon(
painter = painterResource(id = R.drawable.ic_up_chevron),
modifier = Modifier
.rotate(rotation),
contentDescription = "",
)
}
}

And here is the result. It looks much better with animations.

Next Topics?

  1. Add Pull to refresh & Sticky Headers
  2. Make it reusable with generics

--

--

Mrugendra Thatte
Mrugendra Thatte

Written by Mrugendra Thatte

Android Platform Lead — Mobile | AR/VR Enthusiast | Photography & Drone Enthusiast | Leadership | Australia

No responses yet