Circular Progress Bar with Jetpack compose: A Step-by-Step Guide

Esther Carrelle Rangira
5 min readMay 15, 2023

--

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:

Circular progress bar with different remaining times

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 and totalTime.
  • Inside the function, an instance of updateTransition is created to handle the transition animation.
  • The progress value is animated using the animateFloat function, specifying a transitionSpec of tween with a duration of 800 milliseconds and a linear easing.
  • The color value is animated using the animateColor function, specifying a transitionSpec of tween 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 is red. Otherwise, it's set to green.
  • The updated progress and color values are encapsulated in an instance of CircularTransitionData using the remember 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:

--

--