Jetpack Compose Trick: Progress Bar That Reveals Text with Dynamic Color Blend

Harsh Prasad
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

  1. Box Layout: Used as a container for background, foreground progress, and the text label.
  2. drawWithContent: Allows us to layer the filled portion of the bar over the text.
  3. BlendMode.SrcOut: This is the key! It makes the progress rectangle act like a clipping mask, changing the text’s color dynamically where overlapped.
  4. Layer Save/Restore: Ensures smooth drawing without artifacts.

Pro Tips

  • Want an animation? Wrap progress in animateIntAsState().
  • Want a vertical version? Flip dimensions and use drawRect accordingly.
  • Want to show progress percentage? Replace the label dynamically:
    label = "${(progress * 100 / maxProgress)}%"

--

--

Harsh Prasad
Harsh Prasad

Written by Harsh Prasad

Hii, I am software engineer. Still learning.

Responses (1)