Circular Progress Bar with Jetpack compose: A Step-by-Step Guide
Introduction
Circular progress bars are a popular and effective way to visualize the progress of tasks, downloads, or any other process in mobile app development. With the advent of Jetpack Compose, Google’s modern UI toolkit for building native Android apps, creating beautiful and interactive user interfaces has become more intuitive and efficient than ever before.
In this step-by-step guide, we’ll explore how to build a stunning circular progress bar using Jetpack Compose. We’ll delve into the core concepts of Compose, leverage its powerful declarative syntax, and harness the flexibility it offers to customize and animate our progress bar.
Whether you’re new to Compose or looking to enhance your UI skills, this tutorial will provide you with the knowledge and confidence to create engaging circular progress bars that elevate the user experience of your Android applications. Here are some screenshots about what we need to achieve:
Step 1: Create Android Studio Project
Here we gooo..!!
@Composable
fun CircularTimer(
transitionData: CircularTransitionData,
modifier: Modifier = Modifier
) {
Canvas(
modifier = modifier
.fillMaxWidth()
.requiredHeight(CIRCULAR_TIMER_RADIUS.dp)
)
}
In the code above, we define the CircularTimer
composable function that takes in a transitionData
parameter of type CircularTransitionData
. This data will include the progress value and the color to be used for drawing the progress bar. The modifier
parameter allows for customization of the component's appearance and layout.
@Composable
fun CircularTimer(
transitionData: CircularTransitionData,
modifier: Modifier = Modifier
) {
Canvas(
modifier = modifier
.fillMaxWidth()
.padding(32.dp)
.requiredHeight(CIRCULAR_TIMER_RADIUS.dp)
) {
inset(size.width / 2 - CIRCULAR_TIMER_RADIUS, size.height / 2 - CIRCULAR_TIMER_RADIUS) {
drawCircle(
color = gray,
radius = CIRCULAR_TIMER_RADIUS,
center = center,
style = Stroke(width = 70f, cap = StrokeCap.Round)
)
drawArc(
startAngle = 270f, // 270 is 0 degree
sweepAngle = transitionData.progress,
useCenter = false,
color = transitionData.color,
style = Stroke(width = 70f, cap = StrokeCap.Round)
)
}
}
}
By using the Canvas
composable, we have the power to shape and mold our circular progress bar into something truly enchanting. So, grab your virtual paintbrushes, and let's add some flair to your app's user interface!
Inside the Canvas
composable, we use the drawCircle
function to draw the background circle of the progress bar. We set the color to gray
and define the radius and stroke width to achieve the desired thickness.
Next, we use the drawArc
function to draw the actual progress arc. The startAngle
is set to 270 degrees, which represents the starting point of the progress bar. The sweepAngle
is provided by the transitionData.progress
value, which determines the current progress of the task. The color
property is set to transitionData.color
to reflect the appropriate color based on the progress value.
Now that we have our CircularTimer
component, let's move on to the implementation of the circular transition data.
class CircularTransitionData(
progress: State<Float>,
color: State<Color>
) {
val progress by progress
val color by color
}
To animate the circular progress bar, we’ll use the updateTransition
function to define transitions for the progress and color. The updateCircularTransitionData
function calculates the progress and color based on the remaining time and total time.
@Composable
fun updateCircularTransitionData(
remainingTime: Long,
totalTime: Long
): CircularTransitionData {
val transition = updateTransition(targetState = remainingTime)
val progress = transition.animateFloat(
transitionSpec = { tween(800) }
) { remTime ->
if (remTime < 0) {
360f // Completed, show full circle
} else {
360f - ((360f / totalTime) * (totalTime - remTime))
}
}
}
To animate the circular progress bar and update its color dynamically, I’ll introduce the updateCircularTransitionData
function. This function takes in the remaining time and total time as parameters and returns a CircularTransitionData
object with the updated progress and color values.
@SuppressLint("UnusedTransitionTargetStateParameter")
@Composable
fun updateCircularTransitionData(
remainingTime: Long,
totalTime: Long
): CircularTransitionData {
...
val color = transition.animateColor(
transitionSpec = {
tween(800, easing = LinearEasing)
}, label = "Color transition"
) {
if (progress.value < 180f && progress.value > 90f) {
yellow
} else if (progress.value <= 90f) {
red
} else {
green
}
}
return remember(transition) { CircularTransitionData(progress = progress, color = color) }
}
updateCircularTransitionData
function:
- This function is responsible for updating the circular progress bar’s transition data based on the remaining time and total time values.
- It takes two parameters:
remainingTime
andtotalTime
. - Inside the function, an instance of
updateTransition
is created to handle the transition animation. - The
progress
value is animated using theanimateFloat
function, specifying a transitionSpec oftween
with a duration of 800 milliseconds and a linear easing. - The
color
value is animated using theanimateColor
function, specifying a transitionSpec oftween
with the same duration and easing. - The color of the progress bar is determined based on the current progress value. If the progress is between 90 and 180 degrees, the color is set to
yellow
. If it's less than or equal to 90 degrees, the color isred
. Otherwise, it's set togreen
. - The updated progress and color values are encapsulated in an instance of
CircularTransitionData
using theremember
function, ensuring that the transition data is remembered across recompositions.
Kudos for making it till the end of the article, you can now enjoy your colorful circular progress bar made with Jetpack compose.
I’d enjoy getting any feedback about this article; you can connect with me on LinkedIn and Github
https://www.linkedin.com/in/esther-carrelle-rangira
https://github.com/esthcarelle
References: