How to create Expandable List in Jetpack Compose — Part 2 — Animations
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
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?
- Add Pull to refresh & Sticky Headers
- Make it reusable with generics