Switching between Composables with Jetpack Compose Animations

Seda K
5 min readDec 12, 2023

In the dynamic world of Android app development, Jetpack Compose has emerged a powerful toolkit, revolutionising the way we create user interfaces. It comes with all sorts of ready made Composables that can present list data beautifully.

In this article, we’ll delve into the versatility of Jetpack Compose by exploring the seamless transition between two fundamental Composables: LazyVerticalGrid and LazyColumn. Join me as we see how easy it is to switch between the two, uncovering tips and tricks along the way to enhance your Compose development experience.

The setup

Step 1: A simple Card

For this to make sense, I’ll need to work incrementally so let’s start by creating a card that will later be displayed as either a grid or a list.

@Composable
fun AlbumCard(album: Album, modifier: Modifier = Modifier) {
Card(
modifier = modifier
.padding(8.dp)
.fillMaxWidth(),
elevation = CardDefaults.cardElevation(
defaultElevation = 2.dp
),
colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surface),
) {
Row {
Image(
painter = painterResource(id = R.drawable.ic_launcher_background),
contentDescription = "random image",
contentScale = ContentScale.Fit,
modifier = Modifier.padding(8.dp),
)
Column {
Text(
text = album.title,
modifier = Modifier.padding(top = 8.dp, end = 16.dp),
style = MaterialTheme.typography.titleMedium
)
Text(
text = album.artist,
modifier = Modifier.padding(top = 4.dp),
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}

data class Album(
val title: String,
val artist: String,
)

Step 2: Adding the card to the screen

Now we need to actually display the card on our screen. I’ll create another composable called TopAlbumsScreen which takes our card then uses a LazyColumn to display our list as a vertically scrollable list of AlbumCards. This screen can then be added to the MainActivity’s setContent method.

@Composable
fun TopAlbumsScreen() {
Scaffold(
topBar = {
MainAppBar()
},
content = { padding ->
Surface(
modifier = Modifier.padding(padding),
color = MaterialTheme.colorScheme.background,
) {
val albums = Datasource().loadEntries()
LazyColumn {
items(albums) { album ->
AlbumCard(album)
}
}
}
}
)
}

Your Card should look something like this now:

The implementation

Step 3: Creating the Grid Card

Now, we need to create a new Card which will display the data as a grid rather than a list. It’s pretty similar to the existing AlbumCard except instead of a Row as the root element, we have a Column. To keep things clear, I’ll call this new card AlbumCardGrid and rename the existing AlbumCard to something more specific like AlbumCardList.

@Composable
fun AlbumCardGrid(album: Album, modifier: Modifier = Modifier) {
Card(
modifier = modifier
.padding(8.dp)
.fillMaxWidth(),
elevation = CardDefaults.cardElevation(
defaultElevation = 2.dp
),
colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surface),
) {
Column(modifier = Modifier.padding(8.dp)) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_background),
contentDescription = "random image",
contentScale = ContentScale.Fit,
modifier = Modifier.padding(8.dp),
)
Text(
text = album.title,
modifier = Modifier.padding(top = 8.dp),
style = MaterialTheme.typography.titleSmall
)
Text(
text = album.artist,
modifier = Modifier.padding(top = 4.dp),
style = MaterialTheme.typography.bodyMedium
)
}
}
}

Step 4: Creating the new AlbumCard

This is where things start to get a bit more interesting. We should now have two Cards; AlbumCardGrid and AlbumCardList. Since AlbumCard no longer exists, I’ll create a new AlbumCard which will choose between the two cards depending on some value. In this example, the value is a boolean named isList.

@Composable
fun AlbumCard(
albums: List<Album>,
isList: Boolean,
) {
AnimatedVisibility(
visible = !isList,
enter = fadeIn(),
exit = fadeOut(),
) {
LazyVerticalGrid(columns = GridCells.Fixed(3)) {
items(items = albums) { album ->
AlbumCardGrid(album = album)
}
}
}

AnimatedVisibility(
visible = isList,
enter = fadeIn(),
exit = fadeOut(),
) {
LazyColumn {
items(albums) { album ->
AlbumCardList(album)
}
}
}
}

You’ll see that we used theAnimatedVisibility composable which animates the appearance and disappearance of its content and allows you to hide or show content easily.

Step 5: Managing State

Now that the AlbumCard has changed, our TopAlbumScreen composable will need to look a little bit different too. The main change is that there is now an isList variable created, which is a rememberSaveable function and is true by default. The rememberSaveable function is nifty because it helps you retain state across recomposition and configuration changes.

@Composable
fun TopAlbumsScreen() {
var isList by rememberSaveable { mutableStateOf(true) }

Scaffold(
topBar = {
MainAppBar(
isList = isList,
onLayoutChangeRequested = { isList = !isList })
},
content = { padding ->
Surface(
modifier = Modifier.padding(padding),
color = MaterialTheme.colorScheme.background,
) {
val albums = Datasource().loadEntries()
AlbumCard(
albums = albums,
isList = isList
)
}
}
)
}

If you’re paying close attention, you’ll notice that the MainAppBar has also changed, this is because the option to switch between grid and list view is done in the TopAppBar. Have a look at the TopAppBar implementation below, paying close attention to the actions parameter.

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainAppBar(isList: Boolean, onLayoutChangeRequested: () -> Unit) {
TopAppBar(
title = { Text("Knockoff Spotify") },
actions = {
IconButton(onClick = {
onLayoutChangeRequested()
}) {
if (!isList) {
Icon(
imageVector = Icons.Filled.List,
contentDescription = "list view",
)
}
else {
Icon(
imageVector = Icons.Filled.GridView,
contentDescription = "grid view",
)
}
}
},
colors = TopAppBarDefaults.topAppBarColors(MaterialTheme.colorScheme.primaryContainer),
)
}

You can see that in order to know which icon to show, we use the isList state parameter which has been hoisted from the TopAlbumScreen. When the icon is clicked, we also call the onLayoutChangeRequested() function which will change the value of isList from true to false and vice versa.

The final product will look something like this, albeit without all the cool pictures.

If you’d like to see the full project on github, you can check it out here. Thanks for sticking around!

If you found this article useful, follow me on Medium or connect with me on LinkedIn.

--

--

Seda K

A software engineer with one foot in mobile development and the other in backend.