Jetpack Compose Gradient Progress Bar

jetpack compose custom progress indicator

This is a linear gradient progress bar made with Android Jetpack Compose. It has round corners. It uses Canvas API to draw the foreground and background bars. I have also added animation to the foreground bar.

The default Jetpack Compose LinearProgressIndicator offers customizations like round corners, height, animation duration, size, and colors. But, when I set the rounded corners, it wasn’t perfect. So, I have used the Canvas API to make this custom linear progress indicator. The composable takes parameters like height, color, padding, gradient color set, animation delay, and duration. You can customize them as per your requirements.

The progress will be updated automatically using viewModel. When you launch the activity, it starts a background thread inside the viewModel.launch block. The background thread updates the progress bar using the MutableLiveData variable. While changing the progress, the foreground bar moves forward with a smooth animation.

The Jetpack Compose progress bar has two bars. They are foreground and background. The background bar or indicator has a light grey color (grey color with 0.3f alpha) and the foreground bar has a gradient color. It gets animated every time progress is changed. I’m using animateFloatAsState API for animation and observeAsState API to update the progress bar. The foreground bar starts at the left and moves to the right by changing the position along the x-axis. I set the animation duration to 1 second and the delay to 0 seconds. But, you can change them according to your requirements.

The progress bar shows the progress in the form of text using the Text composable. I put it below the progress bar. Both the Canvas and Text are wrapped in a Column composable with alignment and arrangement equal to the center.

Make sure that you are using the latest Android Studio version before adding this code to your project. Jetpack Compose doesn’t work in the older versions.

    Final Output:

    If the video isn’t working, watch it on the YouTube.

    Helpful Links to Understand the Code:

    Resources Used in the Project:

    Here are the Gradle files used in the project.

    Jetpack Compose Progress Bar Source Code:


    import android.os.Bundle
    import androidx.activity.ComponentActivity
    import androidx.activity.compose.setContent
    import androidx.compose.animation.core.animateFloatAsState
    import androidx.compose.animation.core.tween
    import androidx.compose.material.*
    import androidx.compose.runtime.*
    import androidx.compose.runtime.livedata.observeAsState
    import androidx.compose.ui.Alignment
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.geometry.Offset
    import androidx.compose.ui.text.TextStyle
    import androidx.compose.ui.text.font.Font
    import androidx.compose.ui.text.font.FontFamily
    import androidx.compose.ui.text.font.FontWeight
    import androidx.compose.ui.unit.Dp
    import androidx.compose.ui.unit.dp
    import androidx.compose.ui.unit.sp
    import androidx.lifecycle.MutableLiveData
    import androidx.lifecycle.ViewModel
    import androidx.lifecycle.viewModelScope
    import kotlinx.coroutines.Dispatchers
    import kotlinx.coroutines.delay
    import kotlinx.coroutines.launch
    import kotlinx.coroutines.withContext
    You can use the following code for commercial purposes with some restrictions.
    Read the full license here:
    For more designs with source code,
    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            setContent {
                BlogPostsTheme(darkTheme = false) {
                        modifier = Modifier
                            .background(color = MaterialTheme.colors.background),
                        verticalArrangement = Arrangement.Center,
                        horizontalAlignment = Alignment.CenterHorizontally
                    ) {
    fun GradientProgressbar1(
        viewModel: MyViewModel = androidx.lifecycle.viewmodel.compose.viewModel(),
        indicatorHeight: Dp = 24.dp,
        backgroundIndicatorColor: Color = Color.LightGray.copy(alpha = 0.3f),
        indicatorPadding: Dp = 24.dp,
        gradientColors: List<Color> = listOf(
        numberStyle: TextStyle = TextStyle(
            fontFamily = FontFamily(Font(R.font.roboto_bold, FontWeight.Bold)),
            fontSize = 32.sp
        animationDuration: Int = 1000,
        animationDelay: Int = 0
    ) {
        val downloadedPercentage by viewModel.downloadedPercentage.observeAsState(initial = 0f)
        val animateNumber = animateFloatAsState(
            targetValue = downloadedPercentage,
            animationSpec = tween(
                durationMillis = animationDuration,
                delayMillis = animationDelay
        LaunchedEffect(Unit) {
            modifier = Modifier
                .padding(start = indicatorPadding, end = indicatorPadding)
        ) {
            // Background indicator
                color = backgroundIndicatorColor,
                cap = StrokeCap.Round,
                strokeWidth = size.height,
                start = Offset(x = 0f, y = 0f),
                end = Offset(x = size.width, y = 0f)
            // Convert the downloaded percentage into progress (width of foreground indicator)
            val progress =
                (animateNumber.value / 100) * size.width // size.width returns the width of the canvas
            // Foreground indicator
                brush = Brush.linearGradient(
                    colors = gradientColors
                cap = StrokeCap.Round,
                strokeWidth = size.height,
                start = Offset(x = 0f, y = 0f),
                end = Offset(x = progress, y = 0f)
        Spacer(modifier = Modifier.height(8.dp))
            text = downloadedPercentage.toInt().toString() + "%",
            style = numberStyle
    class MyViewModel : ViewModel() {
        var downloadedPercentage = MutableLiveData<Float>()
        fun startThreadGradient() {
            viewModelScope.launch {
                withContext(Dispatchers.Default) {
                    val totalDownloadSize = 1024f
                    var downloadedSize = 0f
                    while (true) {
                        downloadedSize += ((1..100).random().toFloat())
                        if (downloadedSize < totalDownloadSize) {
                            withContext(Dispatchers.Main) {
                                downloadedPercentage.value =
                                    ((downloadedSize / totalDownloadSize) * 100)
                        } else {
                            withContext(Dispatchers.Main) {
                                downloadedPercentage.value = 100f


    2 thoughts on “Jetpack Compose Gradient Progress Bar”

    1. While the compose LinearProgressIndicator` isn’t quite as customizable as this, the information on it in this article is inaccurate. It’s still fairly customizable. Color, corner radius, animation, and size are all customizable with modifiers.


    Leave a Comment