JetpackCompose - Horizontal Pager Indicator click functionality

I am working on sample where I want to display items using HorizontalPager. I am able to display pages horizontally scrollable without issues. Also able to place indicators using HorizontalPagerIndicator.
Issue: When I click on pager Indicator I am able to navigate forward without any issues. I have 4 pages , when clicked on zerothIndicator , page is scrolled to zeroth indicator, when clicked on 1st indicator page is scrolled to 1st page, when again clicked on zeroth Indicator instead of scrolling to Zeroth page , its scrolled to 2nd page. I am not able to track the position of clicked indicator. Is there a way can we track the position of clicked indicator. Please find my code below for reference:

   @OptIn(ExperimentalFoundationApi::class)
@Composable
fun HorizontalPagerWithIndicators(deals: List<Deal>) {
    val pagerState = rememberPagerState()
    val coroutineScope = rememberCoroutineScope()
      Column{
        HorizontalPager(pageCount = deals.size, state = pagerState,
            contentPadding = PaddingValues(horizontal = 20.dp), pageSpacing = 10.dp) { page ->
             Column() {
                 DisplayDeal(deal = deals[page])

                 Box(
                     modifier = Modifier
                         .align(Alignment.CenterHorizontally)
                         .padding(top = 10.dp, bottom = 10.dp)
                 ) {
                     HorizontalPagerIndicator(
                         pageCount = deals.size,
                         pagerState = pagerState,
                         modifier = Modifier.align(Alignment.Center).clickable {
                             val currentPage = pagerState.currentPage
                             val totalPages = deals.size
                             val nextPage = if (currentPage < totalPages - 1) currentPage + 1 else 0
                             coroutineScope.launch { pagerState.animateScrollToPage(nextPage) }
                         }

                     )
                 }
             }
           
        }
      }


}


Below are the dependencies used:

implementation 'androidx.compose.foundation:foundation:1.4.3'
    implementation "com.google.accompanist:accompanist-pager-indicators:0.30.1"


Please help me in resolving this issue.
Thanks in Advance.

Upvotes: 3

Views: 2808

Answers (1)

Darius Mertin
Darius Mertin

Reputation: 21

You can just copy and modify the implementation of HorizontalPagerIndicator.

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ClickableHorizontalPagerIndicator(
    pagerState: androidx.compose.foundation.pager.PagerState,
    pageCount: Int,
    modifier: Modifier = Modifier,
    pageIndexMapping: (Int) -> Int = { it },
    activeColor: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
    inactiveColor: Color = activeColor.copy(ContentAlpha.disabled),
    indicatorWidth: Dp = 8.dp,
    indicatorHeight: Dp = indicatorWidth,
    spacing: Dp = indicatorWidth,
    indicatorShape: Shape = CircleShape,
) {
    val scope = rememberCoroutineScope()

    val stateBridge = remember(pagerState) {
        object : PagerStateBridge {
            override val currentPage: Int
                get() = pagerState.currentPage
            override val currentPageOffset: Float
                get() = pagerState.currentPageOffsetFraction
        }
    }

    HorizontalPagerIndicator(
        pagerState = stateBridge,
        pageCount = pageCount,
        modifier = modifier,
        pageIndexMapping = pageIndexMapping,
        activeColor = activeColor,
        inactiveColor = inactiveColor,
        indicatorHeight = indicatorHeight,
        indicatorWidth = indicatorWidth,
        spacing = spacing,
        indicatorShape = indicatorShape,
    ) {
        scope.launch {
            pagerState.animateScrollToPage(it)
        }
    }
}

@Composable
private fun HorizontalPagerIndicator(
    pagerState: PagerStateBridge,
    pageCount: Int,
    modifier: Modifier = Modifier,
    pageIndexMapping: (Int) -> Int = { it },
    activeColor: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
    inactiveColor: Color = activeColor.copy(ContentAlpha.disabled),
    indicatorWidth: Dp = 8.dp,
    indicatorHeight: Dp = indicatorWidth,
    spacing: Dp = indicatorWidth,
    indicatorShape: Shape = CircleShape,
    onIndicatorClick: (Int) -> Unit,
) {

    val indicatorWidthPx = LocalDensity.current.run { indicatorWidth.roundToPx() }
    val spacingPx = LocalDensity.current.run { spacing.roundToPx() }

    Box(
        modifier = modifier,
        contentAlignment = Alignment.CenterStart
    ) {
        Row(
            horizontalArrangement = Arrangement.spacedBy(spacing),
            verticalAlignment = Alignment.CenterVertically,
        ) {

            repeat(pageCount) {
                Box(
                    modifier = Modifier
                        .size(width = indicatorWidth, height = indicatorHeight)
                        .clip(indicatorShape)
                        .background(color = inactiveColor)
                        .clickable { onIndicatorClick(it) }  //modified here
                )
            }
        }

        Box(
            Modifier
                .offset {
                    val position = pageIndexMapping(pagerState.currentPage)
                    val offset = pagerState.currentPageOffset
                    val next = pageIndexMapping(pagerState.currentPage + offset.sign.toInt())
                    val scrollPosition = ((next - position) * offset.absoluteValue + position)
                        .coerceIn(
                            0f,
                            (pageCount - 1)
                                .coerceAtLeast(0)
                                .toFloat()
                        )

                    IntOffset(
                        x = ((spacingPx + indicatorWidthPx) * scrollPosition).toInt(),
                        y = 0
                    )
                }
                .size(width = indicatorWidth, height = indicatorHeight)
                .then(
                    if (pageCount > 0) Modifier.background(
                        color = activeColor,
                        shape = indicatorShape,
                    )
                    else Modifier
                )
        )
    }
}

Also you have to add the PagerStateBridge interface since it is internal.

internal interface PagerStateBridge {
    val currentPage: Int
    val currentPageOffset: Float
}

Upvotes: 2

Related Questions