androidkotlinandroid-jetpack-compose

I have stacked cards. How to implement an nimation for stacked items in horizontal pager?


I want that during a right swipe, the first element disappears and the lower card (child) appears on top, essentially using a fade-out and zoom-in animation

My code. Looks like my animation's code works incorrectly

 val pagerState = rememberPagerState(initialPage = 0) {
        pagerCount
    }
    Box(modifier = modifier) {
        for (i in 0 until pagerCount - 1) {
            Column(
                modifier = Modifier
                    .padding(
                        top = Spaces.xs2 * (pagerCount - i),
                        start = Spaces.xs2 * (pagerCount - i),
                        end = Spaces.xs2 * (pagerCount - i),
                    )

            ) {
                item(i.toFloat())
                if (i == 1) {
                    button(Spaces.xs2 * (pagerCount - i))
                }
            }

            HorizontalPager(
                modifier = modifier,
                state = pagerState,
            ) { page ->
                Box(
                    modifier = Modifier
                        .graphicsLayer {
                            val pageOffset = pagerState.offsetForPage(page)
                            val scale = 1f - abs(pageOffset) * 0.2f
                            scaleX = scale
                            scaleY = scale
                            alpha = 1f - abs(pageOffset)
                        }
                ) {
                    item(page.toFloat())
                }
            }
        }
    }
}

private fun PagerState.offsetForPage(page: Int): Float {
    return this.currentPageOffsetFraction + (page - this.currentPage)
}

Solution

  • I am not sure what exactly you want to achieve, but the closest thing to what you are describing that can be achieved using HorizontalPager is like this:

    @Composable
    fun SpecialPager() {
        val pagerCount = 5
        val pagerState = rememberPagerState(initialPage = 0) {
            pagerCount
        }
    
        HorizontalPager(
            state = pagerState,
        ) { page ->
    
            Column(
                modifier = Modifier
                    .fillMaxSize()
                    .graphicsLayer {
                        // Calculate the absolute offset for the current page from the
                        // scroll position. We use the absolute value which allows us to mirror
                        // any effects for both directions
                        val pageOffset = (pagerState.currentPage - page + pagerState.currentPageOffsetFraction).absoluteValue
    
                        // We animate the alpha, between 50% and 100%
                        alpha = lerp(
                            start = 0.5f,
                            stop = 1f,
                            fraction = 1f - pageOffset.coerceIn(0f, 1f)
                        )
                        scaleX = lerp(
                            start = 0.5f,
                            stop = 1f,
                            fraction = 1f - pageOffset.coerceIn(0f, 1f)
                        )
                        scaleY = lerp(
                            start = 0.5f,
                            stop = 1f,
                            fraction = 1f - pageOffset.coerceIn(0f, 1f)
                        )
                    },
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Card(
                    modifier = Modifier
                        .width(300.dp)
                        .heightIn(600.dp)
                ) {
                    // some Composable
                }
            }
        }
    }
    

    Output:

    Screen Recording

    If you really want a stack, meaning that the old cards don't slide out of the screen, and the new cards slide in on the top, then I think you cannot achieve this using a HorizontalPager. HorizontalPager uses a lazy list under the hood, and a new item only can enter the screen when the old one scrolls out of the viewport.