I'm trying to create a custom progress bar in Compose, where each step is represented by a dot, and beneath each dot, there's a text label. However, I'm having trouble aligning the text labels so that they are properly centerred under each dot.
This is what i'm trying to achieve:
Here is my current implementation and how it looks:
data class Step(val title: String = "") {}
StepsProgressView(
steps = listOf(Step("text"),Step("something"),Step("some"),Step("DD"),Step("Ssss cccc")),
completedIndex = 2
)
@Composable
fun StepsProgressView(
steps: List<Step>,
modifier: Modifier = Modifier,
lineWidth: Dp = 1.dp,
completedIndex: Int = 2
) {
Column(modifier = modifier) {
Box(contentAlignment = Alignment.Center) {
Canvas(modifier = Modifier.fillMaxWidth()) {
val width = drawContext.size.width
val height = drawContext.size.height
val yOffset = height / 2
val itemWidth = width / steps.size
var startOffset = itemWidth / 2
var endOffset = startOffset
val barWidth = lineWidth.toPx()
repeat(steps.size - 1) { index ->
endOffset += itemWidth
drawLine(
brush = SolidColor(if (index < completedIndex) Color.Blue else Color.Gray),
start = Offset(startOffset, yOffset),
end = Offset(endOffset, yOffset),
strokeWidth = barWidth
)
startOffset = endOffset
}
}
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceAround,
verticalAlignment = Alignment.CenterVertically,
) {
repeat(steps.size) { index ->
Box(contentAlignment = Alignment.Center) {
// last index image
if (index == steps.lastIndex)
Image(
painter = painterResource(id = androidx.biometric.R.drawable.fingerprint_dialog_fp_icon),
contentDescription = null,
modifier = Modifier.size(24.dp),
contentScale = ContentScale.Crop
)
// circles box
else
Box(
modifier = Modifier
.size(12.dp)
.drawBehind {
drawCircle(
color =
if (index <= completedIndex) Color.Blue
else Color.Gray
)
},
contentAlignment = Alignment.Center,
content = {})
}
}
}
}
Spacer(Modifier.height(8.dp))
Row(
Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceAround
) {
steps.forEachIndexed { index, step ->
Text(
text = step.title,
style = TextStyle(
color =
if (steps.indexOf(step) <= completedIndex) Color.Blue
else Color.Gray,
fontSize = 13.sp,
textAlign = TextAlign.Center
),
modifier = Modifier
.wrapContentWidth()
.weight(1f)
)
}
}
}
}
The issue I'm facing is that the text labels beneath the dots are not perfectly aligned in the center. I want each text label to be directly centered below its corresponding dot.
Any suggestions would be highly appreciated. Thank you!
The problem is that your last index image is larger than the other circles. That messes with the arrangement in your Row that depends on weight(1f)
to distribute the space evenly.
One way to solve that is to make the last index image measure with the same size as the other circles and then paint the image with the desired size nonetheless:
// last index image
if (index == steps.lastIndex)
Box(modifier = Modifier.size(12.dp)) {
Image(
painter = painterResource(id = R.drawable.barImage),
contentDescription = null,
modifier = Modifier.requiredSize(24.dp),
contentScale = ContentScale.Crop
)
}
// circles box
else
...
You might need to add an additional padding to the Row so that this larger-than-expected image still fits.