Search code examples
androidkotlincomposable

Pager indcater in Android Compose


I am new with android Compose Can Anyone told me if there any way to implement Page indcater in compose without third library ? I am using material design 3 I have tried many solutions But its Dublicated

I try a way with state pager but now its Dublicated 🙂


Solution

  • After many attempts to find an effective solution I found two ways

    The first: Jetpack Compose animated pager dots indicator?

    The Second : Pager indicator for compose

    but you will face some issue about PagerState Class which now is duplicated so you Can use rememberPagerState

    fun PageIndicatorSample() {
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
    
        Spacer(Modifier.height(40.dp))
        val pagerState1 = rememberPagerState(initialPage = 0)
        val coroutineScope = rememberCoroutineScope()
    
        PagerIndicatornew(pagerState = pagerState1, indicatorCount = getAllData.size) {
            coroutineScope.launch {
                pagerState1.scrollToPage(it)
            }
    
        }
    
        HorizontalPager(
            pageCount = getAllData.size,
            state = pagerState1,
        ) {
            // Here You Add what compose You Want... this is just example
            Card(getAllData[it])
        }
      }
     }
    

    For Pager indicator

      @OptIn(ExperimentalFoundationApi::class)
     @Composable
     fun PagerIndicatornew(
    modifier: Modifier = Modifier,
    pagerState: PagerState,
    indicatorCount: Int = 5,
    indicatorSize: Dp = 16.dp,
    indicatorShape: Shape = CircleShape,
    space: Dp = 8.dp,
    activeColor: Color = Color(0xffEC407A),
    inActiveColor: Color = Color.LightGray,
    onClick: ((Int) -> Unit)? = null) {
    val listState = rememberLazyListState()
    val totalWidth: Dp = indicatorSize * indicatorCount + space * (indicatorCount - 1)
    val widthInPx = LocalDensity.current.run { indicatorSize.toPx() }
    val currentItem by remember {
        derivedStateOf {
            pagerState.currentPage
        }
    }
    val itemCount = indicatorCount
    LaunchedEffect(key1 = currentItem) {
        val viewportSize = listState.layoutInfo.viewportSize
        listState.animateScrollToItem(
            currentItem,
            (widthInPx / 2 - viewportSize.width / 2).toInt()
        )
    }
    LazyRow(
        modifier = modifier.width(totalWidth),
        state = listState,
        contentPadding = PaddingValues(vertical = space),
        horizontalArrangement = Arrangement.spacedBy(space),
        userScrollEnabled = false
    ) {
    
        items(itemCount) { index ->
    
            val isSelected = (index == currentItem)
    
            // Index of item in center when odd number of indicators are set
            // for 5 indicators this is 2nd indicator place
            val centerItemIndex = indicatorCount / 2
    
            val right1 =
                (currentItem < centerItemIndex &&
                        index >= indicatorCount - 1)
    
            val right2 =
                (currentItem >= centerItemIndex &&
                        index >= currentItem + centerItemIndex &&
                        index <= itemCount - centerItemIndex + 1)
            val isRightEdgeItem = right1 || right2
    
            // Check if this item's distance to center item is smaller than half size of
            // the indicator count when current indicator at the center or
            // when we reach the end of list. End of the list only one item is on edge
            // with 10 items and 7 indicators
            // 7-3= 4th item can be the first valid left edge item and
            val isLeftEdgeItem =
                index <= currentItem - centerItemIndex &&
                        currentItem > centerItemIndex &&
                        index < itemCount - indicatorCount + 1
    
            Box(
                modifier = Modifier
                    .graphicsLayer {
                        val scale = if (isSelected) {
                            1f
                        } else if (isLeftEdgeItem || isRightEdgeItem) {
                            .5f
                        } else {
                            .8f
                        }
                        scaleX = scale
                        scaleY = scale
    
                    }
    
                    .clip(indicatorShape)
                    .size(indicatorSize)
                    .background(
                        if (isSelected) activeColor else inActiveColor,
                        indicatorShape
                    )
                    .then(
                        if (onClick != null) {
                            Modifier
                                .clickable {
                                    onClick.invoke(index)
                                }
                        } else Modifier
                    )
                )
              }
             }
          }
    

    NOTE: the first solution has some issues with indexing so I prefer the Second one