google / accompanist

A collection of extension libraries for Jetpack Compose
https://google.github.io/accompanist
Apache License 2.0
7.39k stars 597 forks source link

[Pager] animateScrollToPage() animation duration #1261

Closed sebastinto closed 2 years ago

sebastinto commented 2 years ago

Since animationSpec was removed in v0.19.0 are we left with no control over animation duration when calling animateScrollToPage()? Is there a workaround to bring some amount of control back? Thanks!

andkulikov commented 2 years ago

You can call animateScrollBy() on the PagerState, however you will need to manually calculate the amount of pixels to scroll, this is essentially a page size & amount of pages you want to scroll

sebastinto commented 2 years ago

You can call animateScrollBy() on the PagerState, however you will need to manually calculate the amount of pixels to scroll, this is essentially a page size & amount of pages you want to scroll

That works! Thanks for the quick reply!

arifur-nureca commented 1 year ago

Hi @sebastinto Can you share your code how you were able to achieve that?

sebastinto commented 1 year ago

@arifur-nureca I'm animating Card components that auto-scroll forward then back to the start, in a loop so it looks something like this:

States

val pagerState = rememberPagerState()
var pageSize by remember { mutableStateOf(IntSize.Zero) }
val lastIndex by remember(pagerState.currentPage) {
        derivedStateOf {pagerState.currentPage == items.size - 1 }
    }

Scroll

LaunchedEffect(Unit) {
        while (true) {
            yield()
            delay(6000)
            pagerState.animateScrollBy(
                value = if (lastIndex) -(pageSize.width.toFloat() * items.size) else pageSize.width.toFloat(),
                animationSpec = tween(if (lastIndex) 2000 else 1400)
            )
        }
    }

pageSize is assigned with an onSizeChanged Modifier on the Card

HorizontalPager(
        count = items.size,
        state = pagerState
    ) { pageIndex ->
        Card(modifier = modifier.onSizeChanged { pageSize = it } ) { /* card content */ }
    }
imamyusupb commented 1 year ago

@sebastinto i have and issue for your code, please could you solve this. i was follow your step and after swipe the pager, automatic pager didnt working, i put the scale in the box as my container.

sebastinto commented 1 year ago

@sebastinto i have and issue for your code, please could you solve this. i was follow your step and after swipe the pager, automatic pager didnt working, i put the scale in the box as my container.

Swiping doesn't break automatic paging with the code posted above so there must be something going on on your end. Please post your code. Or even better, post your question to StackOverflow because this is probably not the best place for troubleshooting. Feel free to link to this issue though.

imamyusupb commented 1 year ago

@sebastinto

val items = DashboardSlider1.getData()
 val pagerState = rememberPagerState()
    var pageSize by remember { mutableStateOf(IntSize.Zero) }
    val lastIndex by remember(pagerState.currentPage) {
        derivedStateOf { pagerState.currentPage == items.size - 1 }

    LaunchedEffect(Unit) {
        while (true) {
            yield()
            delay(2000)
            pagerState.animateScrollBy(
                value = if (lastIndex) -(pageSize.width.toFloat() * items.size) else pageSize.width.toFloat(),
                animationSpec = tween(if (lastIndex) 2000 else 1400)
            )
        }
    }
    }
Column(
    horizontalAlignment = Alignment.CenterHorizontally,
    modifier = Modifier
        .fillMaxSize()
        .padding(12.dp)
        .verticalScroll(rememberScrollState())
) {
    TopSectionDashboard()
    Spacer(modifier = Modifier.height(20.dp))
    Box {
        HorizontalPager(count = items.size, state = pagerState) { page ->
            AutomaticSliderCard(item = items[page],Modifier.onSizeChanged { pageSize = it })
        }
        Indicators(
            size = items.size,
            index = pagerState.currentPage,
            modifier = Modifier.align(Alignment.BottomCenter)
        )
    }

`

@Composable
fun AutomaticSliderCard(item: DashboardSlider1, modifier: Modifier = Modifier) {
    Box(
        modifier = modifier
            .fillMaxWidth()
            .height(200.dp)
    ) {
        Image(
            painter = painterResource(id = R.drawable.background_dashboard_slider1),
            contentDescription = null,
            modifier = Modifier.fillMaxSize(),
            alignment = Alignment.Center,
            contentScale = ContentScale.Crop,
        )
        Row(
            verticalAlignment = Alignment.CenterVertically,
            modifier = Modifier
                .align(Alignment.CenterStart)
                .offset(y = -20.dp)
        ) {
            Column(horizontalAlignment = Alignment.Start) {
                Text(
                    text = item.title.toString(),
                    fontFamily = Poppins,
                    fontSize = 20.sp,
                    color = Color.White,
                    modifier = Modifier.padding(horizontal = 24.dp)
                )
                Text(
                    text = "${item.numberCount}",
                    fontFamily = Poppins,
                    fontSize = 25.sp,
                    fontWeight = FontWeight.Bold,
                    color = Color.White,
                    modifier = Modifier
                        .padding(horizontal = 24.dp)
                        .offset(y = -10.dp)
                )
            }
            Image(
                painter = painterResource(id = item.image),
                contentDescription = null,
                modifier = Modifier.size(120.dp),
                alignment = Alignment.CenterEnd
            )
        }
    }
}
sebastinto commented 1 year ago

@imamyusupb

Yup I can certainly reproduce. Nice catch!

Try wrapping the LaunchedEffect like this:

val isDragged by pagerState.interactionSource.collectIsDraggedAsState()

if (!isDragged) {
    LaunchedEffect(Unit) {
        while (true) {
            yield()
            delay(2000)
            pagerState.animateScrollBy(
                value = if (lastIndex) - (pageSize.width.toFloat() * items.size) else pageSize.width.toFloat(),
                animationSpec = tween(if (lastIndex) 2000 else 1400)
            )
        }
    }
}

More info on StackOverflow.

This seems to be a pretty solid fix from what I can tell. Let me know if that works you.

imamyusupb commented 1 year ago

@sebastinto thanks, that's very kind of you

SodaSurfer commented 5 months ago

when addingcontentPadding and pageSpacing to the horizontal pager the scolling is messed up. any idea how to calculate the page size with those padding?

sebastinto commented 5 months ago

@SodaSurfer Pager is now officially supported in androidx.compose.foundation.pager (more documentation here).

Playing with the new API for just a second, it seems like contentPadding does NOT affect scrolling but pageSpacing does.

First idea that came to mind was that you could do something like this as a workaround (although there may be a better way):

martymiller commented 5 months ago

You can call animateScrollBy() on the PagerState, however you will need to manually calculate the amount of pixels to scroll, this is essentially a page size & amount of pages you want to scroll

@andkulikov Sorry, but this solution sucks and no one should have to do this. I need to navigate to several different pages backwards and forwards. Suggesting that we measure the number of pixels is like something out of 2012. Jetpack Compose should be better than this. The default animation is too fast, and you should be able to easily adjust it.

andkulikov commented 5 months ago

You can call animateScrollBy() on the PagerState, however you will need to manually calculate the amount of pixels to scroll, this is essentially a page size & amount of pages you want to scroll

@andkulikov Sorry, but this solution sucks and no one should have to do this. I need to navigate to several different pages backwards and forwards. Suggesting that we measure the number of pixels is like something out of 2012. Jetpack Compose should be better than this. The default animation is too fast, and you should be able to easily adjust it.

My comment was written two years ago. Since then the Pager from accompanist (this library) was deprecated as we introduced an official Pager right in the main compose:foundation library. The scroll function from there allows you to pass an animation spec