Skip to content

Instantly share code, notes, and snippets.

@JyotimoyKashyap
Created June 27, 2025 05:52
Show Gist options
  • Save JyotimoyKashyap/ca14d94d1274a467c52e7f994000f37b to your computer and use it in GitHub Desktop.
Save JyotimoyKashyap/ca14d94d1274a467c52e7f994000f37b to your computer and use it in GitHub Desktop.
Progress Button Implementation in Jetpack Compose
/**
* A custom progress button Composable that displays an animated fill
* from left to right, contained within the button's specified shape.
*
* This button behaves like a standard Material Design button but includes
* a visual progress indicator that animates its width from 0% to 100%
* over a given duration. The progress animation is always clipped to match
* the button's corners, ensuring a seamless visual effect.
*
* Example Usage:
* ```kotlin
* ProgressButton(
* text = "Let's Get Started",
* onClick = {
* println("On Click is triggered")
* },
* shape = RoundedCornerShape(corner = CornerSize(12.dp)),
* onProgressComplete = {
* println("Progress is completed")
* }
* )
* ```
*
* @param text The text to display inside the button.
* @param shape The shape of the button. The progress animation will be
* clipped to this shape, ensuring its corners match.
* Defaults to [CircleShape], which creates a pill-like button.
* @param progressDurationMillis The duration of the progress animation in milliseconds.
* Defaults to 5000ms (5 seconds).
* @param onClick The callback to be invoked when the button is clicked.
* @param onProgressComplete The callback to be invoked when the progress animation has fully completed.
* Defaults to an empty lambda, so it's optional.
*/
@Composable
fun ProgressButton(
text: String,
shape: Shape = CircleShape,
progressDurationMillis: Int = 5000,
onClick: () -> Unit,
onProgressComplete: () -> Unit = {}
) {
val color = MaterialTheme.colorScheme.primary
val animatedWithFraction = remember { Animatable(0f) }
val density = LocalDensity.current
val layoutDirection = LayoutDirection.Ltr
val defaultButtonClipShape = shape
// LaunchedEffect to start the animation when the composable enters the composition
LaunchedEffect(Unit) {
animatedWithFraction.animateTo(
targetValue = 1f, // Animate to 1f (100% of width)
animationSpec = tween(durationMillis = progressDurationMillis, easing = LinearEasing)
)
onProgressComplete()
}
Button (
onClick = onClick,
modifier = Modifier
.height(IntrinsicSize.Max)
.drawBehind {
val currentSize = this.size
// Get the size of the drawing area (which is the button's size)
// Get the shorter side of the button (width or height)
val outline = defaultButtonClipShape
.createOutline(
currentSize,
layoutDirection,
density
)
val buttonShapePath = Path().apply { addOutline(outline) }
// Calculate the width of the animating progress rectangle based on the animated value
val progressRectWidth = currentSize.width * animatedWithFraction.value
clipPath(buttonShapePath) {
drawRect(
color = color,
size = Size(
// animate this width to move from 0 .. width
width = progressRectWidth,
height = currentSize.height
),
)
}
},
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f),
contentColor = MaterialTheme.colorScheme.onPrimary
),
shape = shape
) {
Text(
text = text,
style = MaterialTheme.typography.bodyMedium
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment