Composing smooth animation transition with Pager in Jetpack Compose



A dynamic, responsive UI with an animated gradient that changes as you scroll and smooth transitions between cards for both light and dark themes.
In this article, you’ll learn how to create this screen with minimal code using HorizontalPager and other Jetpack Compose features.
Configuring PagerState and Determining Page Offset
PagerState provides an API for managing page scroll state, including:
pageCount — total number of pages
currentPage — the currently displayed page
targetPage — the target page during a scroll
settledPage — the final settled page after scrolling
scrollToPage — jumps to a page without animation
animateScrollToPage — smoothly animates the scroll to a page
The key function we need is getOffsetDistanceInPages(), which calculates the offset of a given page. By passing any page to this function, we can determine its scroll offset relative to the specified starting position.
Let’s get started composing!
We start by creating a PagerState with the desired number of pages:
val pagerState = rememberPagerState { 3 }
To determine the offset of the current page, we use:
val pageOffset = pagerState.getOffsetDistanceInPages(currentPage)
0 : The page is centered and fully visible
0.5 : The page is halfway shifted to the right and may be partially covered on the right
-0.5 : The page is halfway shifted to the left and may be partially covered on the left
1 : The page is fully shifted past the right edge and is almost invisible
-1 : The page is fully shifted past the left edge and is almost invisible

This offset can be used to create various effects and transition animations between pages, depending on the scroll state.
Creating the Basic Layout
First, we’ll build the layout using Scaffold, a linear gradient, a list with HorizontalPager, and card representations.

To render the gradient in both light and dark themes, we could simply use the background modifier, since theme changes are infrequent and the gradient is static.
However, in our case, the color needs to change dynamically based on the scroll offset. Therefore, we should use the drawWithCache modifier to cache the Brush object, as recomputing it repeatedly is expensive.
Alternatively, we can use the remember function for caching instead of drawWithCache.
Since the gradient will be actively changing its color in the background, we need to use one of the following drawing modifiers:
drawBehind
drawWithContent
drawWithCache
Using these drawing modifiers helps avoid unnecessary recompositions, as the drawing will occur in the Render phase rather than the Composition phase, as it would with the background modifier.
Creating Effects and Animations
To achieve specific motion physics for elements, we need to link the scroll offset with the drawing layer using the graphicsLayer { } modifier.
It is highly recommended to use graphicsLayer with a lambda, as this allows us to work directly in the Render phase, avoiding unnecessary recompositions.
Let’s create an extension function for the modifier to make it reusable.
For smooth animations of scale and alpha during page transitions, the lerp function is used to perform linear interpolation. For example, with scaleX and scaleY, it decreases the size as the page moves away from the center. At the center, the scale is 1f, and at the edges, it reduces to 0.7f, creating a depth effect and a smooth transition between elements on adjacent screens.
To achieve a natural page-flipping effect, the EaseOutQuad easing function is applied. This makes the movement fast at the beginning and gradually slows down towards the end, creating a fluid transition.

pageOffset * size.width / 2f — calculates how much the object needs to be shifted. Dividing by 2f reduces the amplitude of the offset to ensure the effect isn’t too harsh.

Now, let’s begin creating the transition for the cards
Left Y rotation → rotation value * pageOffset.absoluteValue
Right Y rotation → -rotation value * pageOffset.absoluteValue
Left Z rotation → -rotation value * pageOffset.absoluteValue
Right Z rotation → rotation value * pageOffset.absoluteValue
In the case of visible offset for the current page to the right, artifacts of the card offset on the adjacent page to the right may appear. To avoid this, we should handle the case when pageOffset >= 1f.

Changing the gradient colors
We will again use the getOffsetDistanceInPages function from PagerState to determine the distance from the current page to the pages with indices 1 and 2. These values, stored in the distanceTo1Page and distanceTo2Page variables, indicate how far each of these pages is from the current position. In these lists, we modify the red and green components of the first two elements using the values of distanceTo1Page and distanceTo2Page. This results in a change in color hue based on the distance to the corresponding pages, creating a smooth color transition effect as the pages are scrolled.

To create the blur effect on the top bar, we will use the Haze library, which provides the ability to create various glassmorphism effects.

Happy Composing 😉
Follow me on LinkedIn