Created
August 7, 2026 18:20
-
-
Save nascimentodiego/95132b621a376462debda4bc73d09965 to your computer and use it in GitHub Desktop.
PageIndicator in compose with PagerState
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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