How to create an endless Pager in Jetpack Compose
Asked Answered
B

2

6
    Box(
        modifier = Modifier.size(100.dp),
        contentAlignment = Alignment.Center
    ) {
        val pagerState = rememberPagerState()
        val items = listOf("A", "B", "C")
        androidx.compose.foundation.pager.HorizontalPager(
            state = pagerState,
            pageCount = items.size,
            modifier = Modifier,
            verticalAlignment = Alignment.CenterVertically
        ) { page ->
            Text(
                text = items[page],
                modifier = Modifier
            )
        }
    }

In the above code, the pager stops scrolling after reaching the last item. However, I want the pager to continue scrolling endlessly after reaching the last item.

sketch

Bathe answered 16/2, 2023 at 6:24 Comment(0)
R
12

You can create it by setting pageCount to Int.MAX_VALUE and getting modulus of current page to get index for your list of items.

@Preview
@Composable
private fun Test() {

    val pageCount = Int.MAX_VALUE
    val items = listOf("A", "B", "C")
    val pagerState = rememberPagerState(
        initialPage = pageCount / 2
    )

    HorizontalPager(
        modifier = Modifier.fillMaxWidth(),
        pageCount = pageCount,
        state = pagerState
    ) {
         Text(text = items[it % 3])
    }
}

Edit

As of Compose version 1.6.0 Int.MAX_VALUE(2147483647) causes pager to have ANR, to prevent this add a big number but smaller than 2147483647 that user won't bother to scroll that far.

@Preview
@Composable
private fun InfinitePagerSample() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
            .border(2.dp, Color.Red)
    ) {

        val items = remember {
            listOf("A", "B", "C")
        }
        val pageCount = items.size * 400

        val pagerState = rememberPagerState(
            initialPage = pageCount / 2,
            pageCount = {
                pageCount
            }
        )

        HorizontalPager(
            modifier = Modifier.fillMaxWidth(),
            state = pagerState,
            beyondBoundsPageCount = 1
        ) {

            val color = if (it % items.size == 0) {
                Color.Red
            } else if (it % items.size != 1) {
                Color.Yellow
            } else Color.Green

            Column(modifier = Modifier.fillMaxSize().background(color)) {
                SideEffect {
                    println("Page $it, composing...")
                }

                Text(text = items[it % 3], modifier = Modifier.fillMaxSize(), fontSize = 70.sp)

            }
        }
    }
}
Rossetti answered 16/2, 2023 at 7:53 Comment(7)
as of Compose 1.6 this is will make the app crashClearance
I will check this out and update this answer. @Clearance thanks for the warningRossetti
Do you have any update on this? I'm getting a crash too with this solution. I tried to use LaunchedEffect too but still getting crash.Honeysuckle
@Honeysuckle The crash is related to the new implementation of remembers, it is caching data so the page count is too big.Clearance
@Roman, you can refer updated answer. Assigning such a big number causes ANR. You can assign some relatively small number or you can have cyclic list that scrolls to first item when last item is reached or last item first item is scrolled but in that case you will need to disable gestures and need to add your own drag logic.Rossetti
I reported the issue to issuetracker.google.com/issues/334977816 but they deleted my ticket which is very strange.Pornocracy
If you don't want to assign big numbers you can also scroll back to first item with a custom pointerInput. You can refer this answerRossetti
S
1

One small addition to this topic. I noticed that whenever the pageCount changes, the rememberPagerState will not be updated. In this case using the HorizontalPager position with % items.size breaks the calculation and leads to wrong results. Here is a sample that does not have this problem:

@Composable
fun InfiniteHorizontalPager(
    pageCount: Int,
    modifier: Modifier = Modifier,
    initialPage: Int = 0,
    content: @Composable PagerScope.(page: Int) -> Unit,
) {
    val max = Short.MAX_VALUE.toInt()
    val half = max / 2
    
    val pagerPositionIndex = initialPage + half - half % pageCount
    val pagerState = rememberPagerState(pageCount = { max }, initialPage = pagerPositionIndex)

    LaunchedEffect(pagerPositionIndex, pageCount) {
        pagerState.scrollToPage(pagerPositionIndex)
    }

    HorizontalPager(
        state = pagerState,
        userScrollEnabled = pageCount > 1,
        modifier = modifier,
    ) { index ->
        val page = index % pageCount
        this.content(page)
    }
}
Seidule answered 16/8 at 12:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.