ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Follow publication

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.

source

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

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

No responses yet

Write a response