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.
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.
A copy-paste solution with as many customizations as I can think of.
Result
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
This is what I use:
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
/**
* Pager indicator
* Adapted from [Pager in Compose](https://developer.android.com/jetpack/compose/layouts/pager#add-page)
*/
@Composable
fun PageIndicator(
numberOfPages: Int,
selectedPage: Int = 0,
selectedColor: Color = Color.White,
defaultColor: Color = Color.Gray,
defaultRadius: Dp = 8.dp,
selectedLength: Dp = 25.dp,
space: Dp = 4.dp,
modifier: Modifier = Modifier
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(space),
modifier = modifier
) {
repeat(numberOfPages) {
Indicator(
isSelected = it == selectedPage,
selectedColor = selectedColor,
defaultColor = defaultColor,
defaultRadius = defaultRadius,
selectedLength = selectedLength,
)
}
}
}
/**
* pager indicator item
*/
@Composable
fun Indicator(
isSelected: Boolean,
selectedColor: Color,
defaultColor: Color,
defaultRadius: Dp,
selectedLength: Dp,
modifier: Modifier = Modifier.height(defaultRadius)
) {
val width by animateDpAsState(
targetValue = if (isSelected) selectedLength else defaultRadius,
animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy)
)
Box(
modifier = modifier
.width(width)
.clip(CircleShape)
.background(color = if (isSelected) selectedColor else defaultColor)
)
}
© 2022 - 2024 — McMap. All rights reserved.