Customizing Compose Pager with fun indicators and transitions 🚥

Rebecca Franks
Android Developers
Published in
6 min readApr 6, 2023

--

The Compose March 2023 release introduces the pager composables: HorizontalPager and VerticalPager. Whilst the documentation has been added with information on how to use pager in the standard way, in this article, we will look at creating some fun indicators and transitions between pages.

Horizontal Pager with custom transitions

Transitions between pages 🔀

The documentation covers the basics for getting access to how far a page is scrolled from the “snapped” position. We can use this information to create transition effects between pages.

For example, if we wanted to create a simple fade effect between pages, we can apply a graphicsLayer modifier to our page composable to adjust its alpha and translationX:

We could then extract this graphicsLayer modifier into a reusable modifier that we could use on other HorizontalPager instances:

This is great, we can achieve the same effects that we’ve previously been able to achieve with ViewPager in views.

Other fun transition effects 🪄

Some more common effects that you’ve been able to achieve with a ViewPager, can also be achieved with Pager in Compose, such as:

Cube in depth effect

Cube in depth effect

Cube out depth effect

Cube out depth effect

Fidget spinner effect

Fidget spinner effect

These implementations and more effects, can be found in this repo implemented in Compose here.

But this is kind of standard Rebecca, we’d only hope we can achieve the same thing we can in views in Compose! 🙄I get you — and I agree. So let’s demo some things that are difficult to achieve in the View system, in Compose.

Adjusting composable content inside the pager based on position

We are accustomed to adjusting the pages themselves, but the beauty of Compose is that we have access to the PagerState inside our pages content too. So we can use this information to perform fun effects, for example to drive animations, hide/scale content, or show content based on the page’s scroll state.

So let’s take a look at how we could implement this inspirational design found on dribble:

First, we create the static version of the components of this screen, by creating a HorizontalPager inside a Box and static Image and Text composables to represent each page.

Now that we have the static version of our songs, we can analyze the design even more, and see what parts of the composable are animated. The first thing that you may notice is that the image inside the card increases in size depending on if it’s the currently selected item or not. The next thing that changes on scroll is the card expands in size, and shows the “drag to listen” text as it expands. To implement both of these elements, we can use the same pagerState.currentPageOffsetFraction and pagerState.currentPage by using these values in different portions of the content inside the page’s composable.

To adjust the image inside the composable, we use Modifier.graphicsLayer { } on the Image.

Taking the pagerState.currentPage and subtracting the page that the song represents, we will know how far away the song is from the center of the pager (being selected). Now we take that value and add pagerState.currentPageOffsetFraction, we now know the fraction of how far the page is scrolled from its snap position. We can then scale the pageOffset between 1f and 1.75f. This value is then used to apply the scaleX and scaleY so as to not distort the image. The scale will be 1.75f when not selected, and 1f when selected.

This results in the following:

The next step is to expand the card to show and hide the “drag to listen” section of the card. Using the same pageOffset value, we can animate the height of a Column composable, and we can animate the alpha:

Running this, we can see the height of each card is now also driven by the amount that the pages have been dragged:

Great — we’ve achieved the page animations that the original design has! The full source can be found here.

Page Indicators 🚦

Now that we’ve seen how to access the PagerState and transform content with it, another common use case for a pager is to add an indicator to communicate what page you are on in the list of pages. With Compose and PagerState, getting access to this information is simple.

To create a basic page indicator, we can do what the documentation suggests, and draw a circle for each page. However, we can also create our own custom pager indicator, such as a line that is segmented at the bottom of the screen, we can change our drawing logic to draw lines instead of circles, and divide the width up equally.

Taking inspiration from this dribble:

First thing we will do is create a HorizontalPager inside a Box, and move our circle indicators to the bottom of the box, overlapping the content inside the pager:

Next, we will change the circle indicators to instead draw a line that has a different color and size if it’s selected vs if it’s not selected. We give each line a weight of 1f initially.

This results in lines for each page, drawn without changing their sizes:

Now, the lines need to animate their change in length too. If the item is selected, it should be the longest line. We will animate the weight, choosing between either 1f for the non-selected page on the right, vs 1.5f for the selected line, and 0.5f for a page that is to the left of the selected page. We use animateFloatAsState to animate between these weights:

The full source for this example can be found here.

Summary

As we’ve explored in this blog post, we can see that working with the PagerState in Compose gives the flexibility to create more intricate pager interactions that were previously quite complicated to achieve. Leveraging the pagerState.currentPage, pagerState.currentPageOffsetFraction variables, we can create pretty complex UI interactions and page indicators.

Made your own custom page transition or indicator? Let me know on https://androiddev.social/riggaroo

Happy Composing! 👋

Thanks to Levi Albuquerque, Jolanda Verhoef and Florina Muntenescu for reviewing this post.

Code snippets license:

Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0

--

--

Rebecca Franks
Android Developers

Android Developer Relations Engineer at Google. London.