Search code examples
android-jetpack-composeviewpagerindicator

Jetpack Compose animated pager dots indicator?


How can I create a dots indicator like this for a HorizontalPager in Jetpack Compose?

I found a few libs and examples, but non of them were animated like this.

enter image description here


Solution

  • A copy-paste solution with as many customizations as I can think of.

    Result

    enter image description here

    Usage

    @Composable
    fun PageIndicatorSample() {
        val numberOfPages = 3
        val (selectedPage, setSelectedPage) = remember {
            mutableStateOf(0)
        }
    
        // NEVER use this, this is just for example
        LaunchedEffect(
            key1 = selectedPage,
        ) {
            delay(3000)
            setSelectedPage((selectedPage + 1) % numberOfPages)
        }
    
        PageIndicator(
            numberOfPages = numberOfPages,
            selectedPage = selectedPage,
            defaultRadius = 60.dp,
            selectedLength = 120.dp,
            space = 30.dp,
            animationDurationInMillis = 1000,
        )
    }
    

    PageIndicator

    @Composable
    fun PageIndicator(
        numberOfPages: Int,
        modifier: Modifier = Modifier,
        selectedPage: Int = 0,
        selectedColor: Color = Color.Blue,
        defaultColor: Color = Color.LightGray,
        defaultRadius: Dp = 20.dp,
        selectedLength: Dp = 60.dp,
        space: Dp = 30.dp,
        animationDurationInMillis: Int = 300,
    ) {
        Row(
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.spacedBy(space),
            modifier = modifier,
        ) {
            for (i in 0 until numberOfPages) {
                val isSelected = i == selectedPage
                PageIndicatorView(
                    isSelected = isSelected,
                    selectedColor = selectedColor,
                    defaultColor = defaultColor,
                    defaultRadius = defaultRadius,
                    selectedLength = selectedLength,
                    animationDurationInMillis = animationDurationInMillis,
                )
            }
        }
    }
    

    PageIndicatorView

    @Composable
    fun PageIndicatorView(
        isSelected: Boolean,
        selectedColor: Color,
        defaultColor: Color,
        defaultRadius: Dp,
        selectedLength: Dp,
        animationDurationInMillis: Int,
        modifier: Modifier = Modifier,
    ) {
    
        val color: Color by animateColorAsState(
            targetValue = if (isSelected) {
                selectedColor
            } else {
                defaultColor
            },
            animationSpec = tween(
                durationMillis = animationDurationInMillis,
            )
        )
        val width: Dp by animateDpAsState(
            targetValue = if (isSelected) {
                selectedLength
            } else {
                defaultRadius
            },
            animationSpec = tween(
                durationMillis = animationDurationInMillis,
            )
        )
    
        Canvas(
            modifier = modifier
                .size(
                    width = width,
                    height = defaultRadius,
                ),
        ) {
            drawRoundRect(
                color = color,
                topLeft = Offset.Zero,
                size = Size(
                    width = width.toPx(),
                    height = defaultRadius.toPx(),
                ),
                cornerRadius = CornerRadius(
                    x = defaultRadius.toPx(),
                    y = defaultRadius.toPx(),
                ),
            )
        }
    }
    

    Also shared the same in this blog - Page Indicator with Jetpack Compose using Canvas and animations