Creating a Smooth Animated Progress Bar in Jetpack Compose: Canvas drawing and Gradient Animation
Welcome 👋
Have you ever wanted to take your app’s user experience to the next level? This article will explore how to create a customizable and captivating Animated Progress Bar.
Forget boring progress bars! Skyrocket the visual appeal of your app 🚀
The Composable Function
Let’s start by defining the AnimatedProgressBar
composable function:
@Composable
fun AnimatedProgressBar(
progress: Float,
modifier: Modifier,
colors: List<Color>,
trackBrush: Brush? = SolidColor(Color.Black.copy(0.16f)),
strokeWidth: Dp = 4.dp,
glowRadius: Dp? = 4.dp,
strokeCap: StrokeCap = StrokeCap.Round,
gradientAnimationSpeed: Int = 2500,
progressAnimSpec: AnimationSpec<Float> = tween(
durationMillis = 720,
easing = LinearOutSlowInEasing
)
) {
// Implementation…
}
⚒️ ️️Parameters breakdown
⚡ progress ➜ The current progress value (0f to 1f).
⚡ modifier ➜ The modifier to be applied to the progress bar.
⚡ colors ➜ List of colors defining the gradient animation.
⚡ trackBrush ➜ Brush for the static track behind the progress line. If null
, no track will be drawn.
⚡ strokeWidth ➜ Controls the thickness of the progress line.
⚡ glowRadius ➜ Adds a glow effect to the progress line. If null
, no effect will be applied.
⚡ strokeCap ➜ Defines the style of the line ends.
⚡ gradientAnimationSpeed ➜ Duration (in milliseconds) of one loop of the gradient animation.
⚡ progressAnimSpec ➜ Animation behavior for the progress line on value changes.
Implementation ✨
Alright, now we can proceed to the implementation.
Animated Gradient
Let’s begin by crafting the main animation: Moving gradient.
So, how do we do that🤔
We create a linear horizontal gradient with colors positioned at specific locations (0 to 1) within the gradient. Then, we animate an offset value from 0 to 1 and restart continuously.
This animation adjusts the positions of each color within the gradient definition. If a color’s position goes beyond 1, we subtract 1 to wrap it around and place it back at the beginning (0).
As a result, the gradient appears to move smoothly to the end, jump back to the start, and repeat the loop, creating a visually appealing animation.
Time to view the code 👀
// Creat an infinite animation transition
val infiniteTransition = rememberInfiniteTransition()
// Animates offset value transition from 0 to 1
val offset by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = gradientAnimationSpeed,
easing = LinearEasing
),
repeatMode = RepeatMode.Restart
)
)
// Creates a brush that updates based on the animated offset
val brush: ShaderBrush by remember(offset) {
object : ShaderBrush() {
override fun createShader(size: Size): Shader {
val step = 1f / colors.size // Calculate step size
val start = step / 2 // Define start position
// Calculate original positions for each color
val originalSpots = List(colors.size) { start + (step * it) }
// Apply animation offset to each color position
val transformedSpots = originalSpots.map { spot ->
val shiftedSpot = (spot + offset)
// Wrap around if exceeds 1
if (shiftedSpot > 1f) shiftedSpot - 1f else shiftedSpot
}
// Combine colors with their transformed positions
val pairs = colors.zip(transformedSpots).sortedBy { it.second }
// Margin for gradient outside the progress bar
val margin = size.width / 2
// Create the linear gradient shader with colors and positions
return LinearGradientShader(
colors = pairs.map { it.first },
colorStops = pairs.map { it.second },
from = Offset(-margin, 0f),
to = Offset(size.width + margin, 0f)
)
}
}
}
💡To create a smooth, uninterrupted animation of the gradient, we draw it wider than the actual progress bar. This is achieved by adding a margin to both ends of the gradient.
Progress Animation
Before we step into drawing the progress bar, let’s write a little animation that will smoothly transition between progress values.
val animatedProgress by animateFloatAsState(
targetValue = progress.coerceIn(0f, 1f),
animationSpec = progressAnimSpec
)
Canvas Drawing
Finally, we can utilize the Canvas
composable to draw the progress bar.
Canvas(modifier) {
val width = this.size.width
val height = this.size.height
// Create a Paint object
val paint = Paint().apply {
// Enable anti-aliasing for smoother lines
isAntiAlias = true
style = PaintingStyle.Stroke
strokeWidth = strokeWidth.toPx()
strokeCap = strokeCap
// Apply the animated gradient shader
shader = brush.createShader(size)
}
// Handle optional glow effect
glowRadius?.let { radius ->
paint.asFrameworkPaint().apply {
setShadowLayer(radius.toPx(), 0f, 0f, android.graphics.Color.WHITE)
}
}
// Draw the track line if specified
trackBrush?.let { tBrush ->
drawLine(
brush = tBrush,
start = Offset(0f, height / 2f),
end = Offset(width, height / 2f),
cap = strokeCap,
strokeWidth = strokeWidth.toPx()
)
}
// Draw the progress line if progress is greater than 0
if (animatedProgress > 0f) {
drawIntoCanvas { canvas ->
canvas.drawLine(
p1 = Offset(0f, height / 2f),
p2 = Offset(width * animatedProgress, height / 2f),
paint = paint
)
}
}
}
Congratulations🥳! We’ve successfully built it👏. For the complete code implementation, you can access it on GitHub Gist🧑💻. Now, let’s explore how we can put it to use.
Advertisement
Are you learning a foreign language and struggling with new vocabulary? Then, I strongly recommend you check out this words-learning app, which will make your journey easy and convenient!
Usage
Here’s a practical example demonstrating how to use the AnimatedProgressBar
with button clicks to simulate progress updates.
// Define the gradient colors
val GradientColors = listOf(
Color.Red,
Color.Yellow,
Color.Green,
Color.Cyan,
Color.Blue,
Color.Magenta
)
var progress by remember { mutableFloatStateOf(0f) }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(32.dp)
) {
AnimatedProgressBar(
progress = progress,
modifier = Modifier.fillMaxWidth(0.9f),
colors = GradientColors
)
Button(
onClick = {
if (progress < 1f) progress += 0.1f else progress = 0f
}
) {
Text("Update Progress")
}
}
The Result 😍
You might also like 👇
Thank you for reading this article!❤️ I hope you’ve found it enjoyable and valuable. Feel free to show your appreciation by hitting the clap👏 if you liked it or follow Kappdev for more exciting articles😊
Happy coding!