Skip to content

Instantly share code, notes, and snippets.

@decodeandroid
Created August 25, 2024 06:29
Show Gist options
  • Select an option

  • Save decodeandroid/a302b74cea29e2fb8289614387450bcd to your computer and use it in GitHub Desktop.

Select an option

Save decodeandroid/a302b74cea29e2fb8289614387450bcd to your computer and use it in GitHub Desktop.
Custom Progress Bar in Jetpack Compose
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
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.drawscope.rotate
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
@Preview(showBackground = true, showSystemUi = true)
@Composable
fun PreviewProgressBars(modifier: Modifier = Modifier) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceAround,
horizontalAlignment = Alignment.CenterHorizontally
) {
MovingBoxProgressBar()
ScalingCirclesProgressBar()
ExpandingBarsProgressBar()
CircularProgressBar()
}
}
@Composable
fun MovingBoxProgressBar() {
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)
)
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
//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
}
}
Column(
modifier = Modifier
.fillMaxWidth()
.graphicsLayer {
rotationZ = 45f
},
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
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)
)
)
}
}
}
}
}
@Composable
fun ScalingCirclesProgressBar() {
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 = ""
)
}
// 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))
) {}
}
}
}
@Composable
fun ExpandingBarsProgressBar() {
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 = ""
)
}
// 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))
)
}
}
}
@Composable
fun CircularProgressBar() {
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
androidx.compose.foundation.Canvas(modifier = Modifier.size(150.dp)) {
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()
// 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)
)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment