Progress Bar using Canvas — Jetpack Compose

Naveen Udesh
4 min readJul 18, 2024

--

Hello Developers…

Today I’m going to share how to create a progress bar using canvas in jetpack compose.I think lot of mobile app developers specially new comers stuck while they developed this progress bars.

In this blog I mainly discuss about 2 types of progress bars.

1.Discrete Progress Bar

2.Continuous Progress Bar

Discrete Progress Bar & Continuous Progress Bar

Discrete Progress Bar

Discrete Progress Bar is where the progress indicator has distinct steps. The black triangle pointer adds emphasis on a specific point of progress.

How can I create this?

import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

@Composable
fun DiscreteProgressBar(
//you can adjest your values in here
valueYouWant: Float,
positiveColor: Color = Color.Green,
remainingColor: Color = Color.Red,
cornerRadius: Float = 10f,
backgroundColor: Color,
startPoint: Float,
widthPoint: Float,
) {
val progressColor =
if (valueYouWant >= 0) {
positiveColor
} else {
remainingColor
}
val progressFraction = kotlin.math.abs(valueYouWant)

Box(
modifier = Modifier.width(212.dp).height(16.dp),
) {
Canvas(
modifier =
Modifier.fillMaxSize()
.padding(vertical = 4.dp),
) {
val barWidth = size.width
val barHeight = 8.dp.toPx()

val progressWidth = barWidth * progressFraction //your progress width is calculated by this section.

drawRoundRect(
color = backgroundColor,
size = Size(barWidth, barHeight),
cornerRadius = CornerRadius(cornerRadius),
)

drawRoundRect(
color = progressColor,
size = Size(widthPoint, barHeight),
cornerRadius = CornerRadius(cornerRadius),
topLeft = Offset(startPoint, 0f),
)
drawArrow(size.width * progressFraction / 2)
}
}
}

fun DrawScope.drawArrow(progressWidth: Float) {
val arrowWidth = 8.dp.toPx() //arrow width
val arrowHeight = 8.dp.toPx()// arrow height

translate(
left = progressWidth - arrowWidth,
top = 8f,
) {
val path =
Path()
.apply {
moveTo(arrowWidth / 2, 4f)
lineTo(arrowWidth, arrowHeight)
lineTo(0f, arrowHeight)
close()
}
drawPath(path, color = Color.Black)
}
}

@Preview // you can get a preview of the DiscreteProgressBar
@Composable
fun DiscreteProgressBarPreview() {
//you can chnage the values what you want in here
DiscreteProgressBar(
valueYouWant = 0.7f, // you can put positive values and negative values in here
backgroundColor = Color(0xFFE1E3E8), //Backgroud color is white
positiveColor= Color(0xFF00876A), //positive color is green
remainingColor = Color(0xFFFF5449), // remaining color is red
startPoint = 80f,
widthPoint = 200f,
)
}

In above code if you pass a positive value (0.5f) positive color is become green, if you pass a negative value (-0.5f) positive color become red.

Continuous Progress Bar

Continuous Progress Bar is which smoothly transitions from start to finish. The green section represents the completed portion, and the red section indicates the remaining part.

How can I create this?

import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.tooling.preview.Preview
import com.naveen.uilibrary.util.scale.scaleHeight
import com.naveen.uilibrary.util.scale.scaleWidth

@Composable
fun ContinuousProgressBar(
yourPercentage: Float,
backgroundColor: Color = Color.Gray,
positiveColor: Color = Color.Green,
remainingColor: Color = Color.Red,
cornerRadius: Float = 20f,
remaningCornerRadius: Float = 20f,
) {
Box(
modifier =
Modifier
.width(212f.scaleWidth())
.height(8f.scaleHeight())
.clip(RoundedCornerShape(10f)),
) {
Canvas(
modifier =
Modifier
.width(212f.scaleWidth())
.height(8f.scaleHeight()),
) {
val barHeight = size.height
val barWidth = size.width
val positiveWidth = barWidth * yourPercentage
val remainingWidth = barWidth - positiveWidth
drawRoundRect(
color = backgroundColor,
size = Size(barWidth, barHeight),
cornerRadius = CornerRadius(cornerRadius),
)
if (yourPercentage > 0f) {
val path =
Path().apply {
moveTo(0f, 0f)
lineTo(positiveWidth- cornerRadius, 0f)
lineTo(positiveWidth, cornerRadius)
lineTo(positiveWidth, barHeight - cornerRadius)
lineTo(positiveWidth- 0, barHeight)
lineTo(0f, barHeight)
close()
}
drawPath(
path = path,
color = positiveColor,
)
}
if (yourPercentage < 1f) {
val path =
Path().apply {
moveTo(positiveWidth- remaningCornerRadius, 0f)
lineTo(positiveWidth+ remainingWidth - 0, 0f)
lineTo(positiveWidth+ remainingWidth, remaningCornerRadius)
lineTo(positiveWidth+ remainingWidth, barHeight - remaningCornerRadius)
lineTo(positiveWidth+ remainingWidth - 0, barHeight)
lineTo(positiveWidth, barHeight)
close()
}
drawPath(
path = path,
color = remainingColor,
)
}
if (yourPercentage > 0f && yourPercentage < 1f) {
drawLine(
color = Color.White,
start = Offset(positiveWidth, barHeight),
end = Offset(positiveWidth- remaningCornerRadius, 0f),
strokeWidth = 4f,
)
}
}
}
}

@Preview
@Composable
fun ContinuousProgressBarPreview() {
ContinuousProgressBar(
yourPercentage = 0.35f,
backgroundColor = Color(0xFFE1E3E8),
positiveColor = Color(0xFF00876A),
remainingColor = Color(0xFFFF5449),
)
}

In above code if you pass a positive value (0.5f) positive color is become green, if you pass a negative value (-0.5f) positive color become red and other remaining part become green or red.

Conclusion

These are common UI elements used to visually indicate the progress of a task or process in applications.

Thank You…

--

--

Naveen Udesh
Naveen Udesh

Written by Naveen Udesh

Mobile Enthusiast - Android Developer #android #kotlin #flutter

No responses yet