Jetpack Compose Trick: Progress Bar That Reveals Text with Dynamic Color Blend
2 min readApr 10, 2025
Introduction
Most of us rely on Android’s built-in LinearProgressIndicator
for progress tracking—but what if you wanted more style and control?
In this tutorial, I’ll show you how to build a fully customizable linear progress bar in Jetpack Compose that has a dynamic text label in the center. As the progress bar fills up, the text underneath blends in and changes color smoothly — just like this:
@Composable
fun GenericProgressBar(
progress: Int,
maxProgress: Int,
label: String = "$progress/$maxProgress",
modifier: Modifier = Modifier,
progressColor: Color = MaterialTheme.colorScheme.primary,
backgroundColor: Color = MaterialTheme.colorScheme.surfaceContainerHighest,
textColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
textColorOnProgress: Color = Color.White,
cornerRadius: Dp = 24.dp
) {
Box(
modifier = modifier
.clip(RoundedCornerShape(cornerRadius))
.background(backgroundColor)
.drawWithContent {
with(drawContext.canvas.nativeCanvas) {
val checkpoint = saveLayer(null, null)
drawContent()
drawRect(
color = progressColor,
size = Size((size.width * progress) / maxProgress, size.height),
blendMode = BlendMode.SrcOut
)
restoreToCount(checkpoint)
}
}
.padding(2.dp),
contentAlignment = Alignment.Center
) {
Text(
text = label,
color = textColor,
style = MaterialTheme.typography.labelSmall
)
}
}
How It Works
- Box Layout: Used as a container for background, foreground progress, and the text label.
drawWithContent
: Allows us to layer the filled portion of the bar over the text.BlendMode.SrcOut
: This is the key! It makes the progress rectangle act like a clipping mask, changing the text’s color dynamically where overlapped.- Layer Save/Restore: Ensures smooth drawing without artifacts.
Pro Tips
- Want an animation? Wrap
progress
inanimateIntAsState()
. - Want a vertical version? Flip dimensions and use
drawRect
accordingly. - Want to show progress percentage? Replace the label dynamically:
label = "${(progress * 100 / maxProgress)}%"