You have seen many type of progress bar in apps like circular progress bar, horizontal progress. In this article you will learn how to make custom progress using Jetpack Compose. Today we will make 4 types of progress bar each with a different look and feel.
1. Moving Box Progress bar.
In this progress bar there are 7 boxes which are keep sliding and filling the empty space one after the another. This progress bar is actually a grid of 3 x 3 where one space is always empty and one space is empty but not static at one place as this empty space keep moving in opposite direction as the box moves. When you first look at the progress bar you feel like the boxes are moving but actually the empty space changes its position which produces effect like the boxes are sliding one after the other.
lets define the a grid of array of size 3 by array of size 3 where the element at 2 into 2 index is always 0 and this will never changes its place. Also create a list of Pair of Integers where each part will hold the information of its index inside the grid. This list will contain all the index inside the grid where the empty box will move. Actually this is the path of the empty box in a certain order.
val grid = Array(3) { Array(3) { 1 } } // 3x3 grid filled with 1s
grid[2][2] = 0 // fixed empty space
//empty box positions list
val emptyPositions = listOf(
Pair(2, 1), Pair(1, 1), Pair(1, 2),
Pair(0, 2), Pair(0, 1), Pair(0, 0),
Pair(1, 0), Pair(2, 0), Pair(2, 1)
)
Now create a variable called currenPositon which will hold the position of the pair in the list which denotes the 2nd Empty box. So move the box we need to create a LaunchedEffect with delay of 300 ms which will also work as the speed of our boxes. Inside this launched effect create a loop and inside this update the current position with each iteration. Then get the index of from the emptyPostions as per the current position. For example the current position is 5 so the index will be 0,0 so this time the empty box will be here and so on.
var currentPos by remember { mutableIntStateOf(0) }
//to get the current position
LaunchedEffect(Unit) {
while (true) {
delay(300) // adjust the speed as needed
currentPos = (currentPos + 1) % emptyPositions.size // 10 % 9 = 1 **keep the index within bounds of a list
}
}
val (row, col) = emptyPositions[currentPos]
grid[row][col] = 0 // moving empty space
After this we need to create a new LaunchedEffect where we will set 1 at all the location inside the grid except the one at where its 0
//to update the location of empty box
LaunchedEffect(key1 = true) {
while (true) {
delay(300) // Adjust the delay as needed
grid.forEachIndexed { rowIndex, rowArray ->
rowArray.forEachIndexed { colIndex, _ ->
grid[rowIndex][colIndex] = 1
}
}
val (newRow, newCol) = emptyPositions[currentPos]
grid[newRow][newCol] = 0
}
}
Now we come to our final step to draw the boxes. So we will loop 3 times and draw a Row composable each time and inside each row we will draw 3 boxes as per the list if its 1 then draw colored box otherwise draw transparent box which will be our empty box
for (i in 0..2) {
Row(
modifier = Modifier
.wrapContentSize()
.padding(1.dp),
horizontalArrangement = Arrangement.spacedBy(1.dp),
verticalAlignment = Alignment.CenterVertically
) {
for (j in 0..2) {
val color = if (grid[i][j] == 0) Color.Transparent else Color.Magenta.copy(0.5f)
Box(
modifier = Modifier
.size(30.dp)
.background(
color = color,
shape = RoundedCornerShape(5.dp)
)
)
}
}
}
2. Scaling Circle Progress bar-
In this progress bar we will draw 3 circles in a row and scale them one by one in sync. We will use animation in jetpack compose to create the transition for our use-case.
Create a variable transition using rememberInfiniteTransition and a list of delays in Integers which will work as sync for out circles.
val transition = rememberInfiniteTransition()
// Animation durations and delays
val duration = 500
val delays = listOf(0, 100, 200)
// Create animations for each circle
val scales = delays.map { delay ->
transition.animateFloat(
initialValue = 0.5f,
targetValue = 1.5f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = duration,
delayMillis = delay,
easing = FastOutSlowInEasing
),
repeatMode = RepeatMode.Reverse
), label = ""
)
}
Now we will show 4 boxes in a row and manipulate the scale modifier of the box to create the scaling animation
// Layout with 3 circles
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.size(150.dp)
) {
scales.forEach { scale ->
Box(
modifier = Modifier
.size(24.dp)
.scale(scale.value)
.clip(CircleShape)
.background(Color.Red.copy(0.5f))
) {}
}
}
3. Expanding bars progress bar –
In this type of progress bar you will see multiple bars in a row expanding and contracting one after the other. We we will use variable like transition and delays but this time the delay have 5 element because our progress bar has 5 vertical bars. Then we will define property of our animation which takes as following argument.
- Initial Value – the value from where we want to start our animation
- target Value – as the name itself explains, value upto which our animation go
- animation Spec-
- animation- which type of animation (tween) you uses
- repeat Mode – define our animation perodicity which can from 2 types Reverse, Restart
val transition = rememberInfiniteTransition()
// Animation durations and delays
val duration = 500
val delays = listOf(0, 100, 200, 300, 400)
// Create animations for each bar
val heights = delays.map { delay ->
transition.animateFloat(
initialValue = 0.2f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = duration,
delayMillis = delay,
easing = FastOutSlowInEasing
),
repeatMode = RepeatMode.Reverse
), label = ""
)
}
Now we need to show our boxes inside a Row composable . In order to animate our bars we will exploit the padding modifier of our Box composable.
// Layout with 5 vertical bars
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.size(150.dp)
) {
heights.forEach { height ->
Box(
modifier = Modifier
.width(20.dp)
.height(100.dp)
.padding(vertical = (100.dp * (1 - height.value) / 2))
.background(color = Color.Green.copy(0.5f), shape = RoundedCornerShape(4.dp))
)
}
}
4. Circular Progress Bar –
This progress bar is similar to the progress bar you see on iOS. Here you see 12 bars arranged around a circle, each bar has a different level of color opacity and with animation they rotate around the center.
Same like the previous, first define our animation and create some variable.
val transition = rememberInfiniteTransition()
// Animation for the rotation angle
// Create rotation animation
val rotation = transition.animateFloat(
initialValue = 360f,
targetValue = 0f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 1500, easing = LinearEasing),
repeatMode = RepeatMode.Restart
), label = ""
)
// Number of bars
val numberOfBars = 12
Now we will use canvas modifier to draw the bars. Here we need some variable like angleStep, barWidth and radius for defining the size of our circular progress bar.
val canvasWidth = size.width
val canvasHeight = size.height
// Calculate the angle between each bar
val angleStep = 360f / numberOfBars
val radius = size.minDimension / 2.2f
val barWidth = 12.dp.toPx(
Then we will loop through the no of bars and with iteration we will draw a rounded corner rectangle around center in circular arrangement.
// Draw animated bars
for (i in 0 until numberOfBars) {
val angle = i * angleStep + rotation.value
val alpha = 1f - (i.toFloat() / numberOfBars)
rotate(degrees = angle, pivot = Offset(canvasWidth / 2, canvasHeight / 2)) {
drawRoundRect(
color = Color.Blue.copy(alpha = alpha),
topLeft = Offset(canvasWidth / 2 - barWidth / 2, canvasHeight / 2 - radius),
size = Size(barWidth, radius / 2),
cornerRadius = CornerRadius(x = barWidth / 2, y = barWidth / 2)
)
}
})
GitHub gist for this video code : here