Cayce K
Cayce K

Reputation: 2338

Jetpack Compose Pager change currentPage

Is there a manual way or an appropriate way to change the current page for a Pager?

I may be breaking some written rules, but essentially I am updating the content of pages, but not changing the number of pages.

The original list might be

A
B
C
D
… (repeat ABCD forever until you cap at Int.MAX_VALUE)

And the next minute it might be

A
B
C
D
E
F
… (same repeat)

I have accurately figured out how to calculate where I want to be in each pager if the number of values changes for any reason, but nothing I’ve done changes the value.

    val pageCount = Int.MAX_VALUE
    val halfSet = (pageCount / 2)
    val offset = halfSet % items.size
    val initial = halfSet - offset
    val initialPage = initial + selected - (initial % items.size) - 1 // This changes
    val pageState = rememberPagerState(
        initialPage = initialPage // This never updates
    )

    LaunchedEffect(
        key1 = pageState,
        key2 = initialPage // this is also constant
    ) {
        snapshotFlow { pageState.currentPage }.collect { page ->
            // Center page is a little strange... So. +1...
            // This definitely breaks or gets weird near the edges.
            //val actualPage = (if(intitalPage != page) intitalPage else page) + 1 // forever returns the first value that intialPage equaled
            val actualPage = page + 1 
            actualSelected = actualPage % items.size
            onChange(actualSelected)
            Log.d(TAG, "$actualPage ${items[actualSelected]} ")
        }
    }

The actualSelected technically is correct now because it does return the value that the page is currently on. I’ve stumbled into that working out. But if A was the selected value and the size of the list changes D might become selected and I really want A to stay. The initalPage is correctly returning to me the position I want it to be, but nothing I’m doing fixes it. And technically scrolling to it causes a ton of recompositions I don’t want. I just want it to “jump” or be at the right page I tell it to be.

Upvotes: 2

Views: 4250

Answers (2)

Stefan Medack
Stefan Medack

Reputation: 2818

Thank you for posting. It helped me a lot in solving the same Bug. But I think your calculations are a bit more complex than they need to be. This solution also works:

@Composable
fun InfiniteHorizontalPager(
    pageCount: Int,
    modifier: Modifier = Modifier,
    initialPage: Int = 0,
    onPageChanged: ((index: Int) -> Unit)? = null,
    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)
    }

    if (onPageChanged != null) {
        LaunchedEffect(pagerState, pageCount) {
            snapshotFlow { pagerState.currentPage }.collect { index ->
                onPageChanged(index % pageCount)
            }
        }
    }

    HorizontalPager(
        state = pagerState,
        userScrollEnabled = pageCount > 1,
        modifier = modifier,
    ) { index ->
        content(index % pageCount)
    }
}

Upvotes: 1

Cayce K
Cayce K

Reputation: 2338

Figured it out…

    val pageCount = Int.MAX_VALUE
    val halfSet = (pageCount / 2)
    val offset = halfSet % items.size
    val initial = halfSet - offset
    val target = initial + selected - (initial % items.size) - 1
    val pageState = rememberPagerState(
        initialPage = target
    )

    LaunchedEffect(
        key1 = items
    ) {
        pageState.scrollToPage(target)
    }

    LaunchedEffect(
        key1 = pageState,
        key2 = items
    ) {
        snapshotFlow { pageState.currentPage }.collect { page ->
            // Center page is a little strange... So. +1...
            // This definitely breaks or gets weird near the edges.
            val actualPage = page + 1
            actualSelected = actualPage % items.size
            onChange(actualSelected)
            if(debug) Log.d(TAG, "$actualPage ${items[actualSelected]} ")
        }
    }

So essentially you have to use 2 LaunchedEffect listeners. I don’t know exactly why as I tried to do the scrollToPage(Int) in the second one before and it didn’t work. If anyone has details on that that would be beneficial I think.

However, this works very well.

Upvotes: 1

Related Questions