Creating a Smooth Fade Transition for Compose Pager Indicators

Osagie Omon
3 min readOct 21, 2023

--

The March 2023 Compose release ushers in the introduction of pager composables, specifically HorizontalPager and VerticalPager. The default methods for pairing the pager with indicators work reasonably well.

However, a recent request from my design team compelled me to go beyond the out-of-the-box options. The pagination bars, representing selected states, were perfectly synchronized, but they lacked the graceful fade-transition style found in other Android carousels. In this article, we’ll delve into how to implement this transition.

Pagination bars (selected states) seem to be timed well, but they are missing the fade-transition style that is present in other carousels on Android. Let’s add this transition.
The fade should track the carousel item. If you hold the cards in place in the middle (50% of the full swipe), the transition of the bars would show it at 50% as well.

Current implementation:

Expected:

I came across Rebecca's article and this formed the bedrock of my thought process.

Calculating Page Offset

Within our PagerState, we have access to a float property known as currentPageOffsetFraction. As the name implies, this property provides us with the offset of the current page, with values ranging from -0.5 to 0.5.

-0.5 == current page is 50% to the right of the the left edge
0.0 == current page is settled along the left edge
0.5 == current page is 50% to the left of the left edge

The next step was to write a function that returns a page’s offset from the centre (current page).

/**
* Get a Page offset from current page
* Negative offset: swiping towards Page
* Positive offset: swiping away from Page
* @param page page index
* @return page offset from current page
*/
fun PagerState.offsetForPage(page: Int) = (currentPage - page) + currentPageOffsetFraction

Having access to the page offset made my work easier. The offset value ranges from:

<= -1.0 == page is off-screen to the right
0.0 == page is settled in the middle and is the current page
>= 1.0 == page is off-screen to the left

Depending on the number of pages, the offset can exceed -1.0 and 1.0. For example, a page located two screens to the left would have an offset of 2.0, while a page three screens to the right would have an offset of -3.0, and so forth.

As a user swipes towards a page on the right, the destination page’s offset value transitions from -1.0 – 0.0. Conversely, the page being swiped away from sees its offset value transition from 0.0 – 1.0. Armed with this knowledge, I harnessed the offset value to calculate the colour opacity for each progress indicator (bar). When a user swipes towards a page, I continuously increase the page indicator’s alpha from 0.0f until the user reaches the page, at which point it’s set to 1.0f. Conversely, for the page the user is swiping away from, I decrease the alpha from 1.0f to 0.0f.

Here’s a code snippet illustrating this concept:

val progressColor = if (offset == 0f) {
activeColor
} else if (offset <= -1f || offset >= 1f) {
inactiveColor.copy(alpha=0f) // Alternative to setting the view invisible
} else {
activeColor.copy(alpha=1-abs(offset))
}

With this code in place, the final step is to apply the progressColorto the indicator. You can find the complete code here.

--

--

Osagie Omon

Mobile Developer . UI/UX Enthusiast . I just bring to life what I see in my head