Creating a Smooth Fade Transition for Compose Pager Indicators
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 edge0.0
== current page is settled along the left edge0.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 right0.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 progressColor
to the indicator. You can find the complete code here.