Skip to content

Instantly share code, notes, and snippets.

@nascimentodiego
Created August 7, 2026 18:20
Show Gist options
  • Select an option

  • Save nascimentodiego/95132b621a376462debda4bc73d09965 to your computer and use it in GitHub Desktop.

Select an option

Save nascimentodiego/95132b621a376462debda4bc73d09965 to your computer and use it in GitHub Desktop.
PageIndicator in compose with PagerState
package com.dfn.animations.ui.compose.coverflow
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.pager.PagerState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import kotlin.math.absoluteValue
/**
* Mesmo indicador em Canvas único (sem Composable/clip/scale por dot — só
* draw, lendo `pagerState` direto no DrawScope, então só invalida o draw,
* não measure/layout/recomposição) usado no onboarding, adaptado pra não
* depender de uma lista de páginas com cor por item: recebe só a
* quantidade de páginas e uma única [selectedColor] pro dot ativo.
*
* @param pagerState estado do pager que este indicador acompanha.
* @param pageCount quantidade de dots a desenhar.
* @param selectedColor cor do dot quando totalmente selecionado.
* @param unselectedColor cor do dot quando totalmente não-selecionado.
* @param dotHeight altura de cada dot (também usada como raio do corner,
* pra manter a ponta sempre arredondada).
* @param minDotWidth largura do dot não-selecionado (também sua altura,
* já que largura=altura vira um círculo perfeito).
* @param maxDotWidth largura do dot totalmente selecionado (o "pill").
* @param gap espaço fixo entre dots.
*/
@Composable
fun PageIndicator(
pagerState: PagerState,
pageCount: Int,
modifier: Modifier = Modifier,
selectedColor: Color = Color.White,
unselectedColor: Color = Color.White.copy(alpha = 0.3f),
dotHeight: Dp = 6.dp,
minDotWidth: Dp = 6.dp,
maxDotWidth: Dp = 24.dp,
gap: Dp = 8.dp,
) {
val totalWidth = minDotWidth * (pageCount - 1) + maxDotWidth + gap * (pageCount - 1)
Canvas(
modifier = modifier
.width(totalWidth)
.height(dotHeight)
) {
val fraction = pagerState.currentPageOffsetFraction // lido no draw -> só invalida o draw
val gapPx = gap.toPx()
val minPx = minDotWidth.toPx()
val maxPx = maxDotWidth.toPx()
var x = 0f
repeat(pageCount) { i ->
val selectedFraction = when (i) {
pagerState.currentPage -> 1f - fraction.absoluteValue
pagerState.currentPage + 1 -> fraction.coerceAtLeast(0f)
pagerState.currentPage - 1 -> (-fraction).coerceAtLeast(0f)
else -> 0f
}
val dotWidth = lerp(minPx, maxPx, selectedFraction)
val color = lerp(unselectedColor, selectedColor, selectedFraction)
drawRoundRect(
color = color,
topLeft = Offset(x, 0f),
size = Size(dotWidth, size.height),
cornerRadius = CornerRadius(size.height / 2f)
)
x += dotWidth + gapPx
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment