Skip to content

Instantly share code, notes, and snippets.

@JustJerem
Last active June 15, 2021 15:35
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JustJerem/b639a452bdfd42c326b53eff835cd935 to your computer and use it in GitHub Desktop.
Save JustJerem/b639a452bdfd42c326b53eff835cd935 to your computer and use it in GitHub Desktop.
Progress circle showing an icon if completed.
import androidx.compose.animation.core.*
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@Composable
fun CircularProgressIndicator(
modifier: Modifier = Modifier,
progress: Float,
progressColor: Color = Blue600,
strokeWidth: Dp = 1.5.dp,
durationInMilliSecond: Int = 500,
startDelay: Int = 0,
radius: Dp = 8.dp
) {
val stroke = with(LocalDensity.current) {
Stroke(width = strokeWidth.toPx())
}
val currentState = remember {
MutableTransitionState(AnimatedArcState.START)
.apply { targetState = AnimatedArcState.END }
}
val animatedProgress = updateTransition(currentState, label = "")
var isFinished by remember { mutableStateOf(false) }
val progress by animatedProgress.animateFloat(
transitionSpec = {
tween(
durationMillis = durationInMilliSecond,
easing = LinearEasing,
delayMillis = startDelay
)
}, label = ""
) { state ->
when (state) {
AnimatedArcState.START -> 0f
AnimatedArcState.END -> progress
}
}
DisposableEffect(Unit) {
isFinished = animatedProgress.currentState == animatedProgress.targetState
onDispose {}
}
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.size(radius * 2)
) {
Canvas(
modifier.size(radius * 2)
) {
val radius = (size.minDimension - stroke.width) / 2
val halfSize = size / 2.0f
val topLeft = Offset(
halfSize.width - radius,
halfSize.height - radius
)
val size = Size(radius * 2, radius * 2)
val sweep = progress * 360 / 100
isFinished = animatedProgress.currentState == animatedProgress.targetState
drawArc(
startAngle = 0f,
sweepAngle = 360f,
color = progressColor,
useCenter = false,
topLeft = topLeft,
size = size,
style = stroke
)
drawArc(
color = progressColor,
startAngle = 270f,
sweepAngle = sweep,
useCenter = true,
topLeft = topLeft,
size = size,
)
}
if (isFinished && progress == 100f) {
Image(painter = painterResource(id = R.drawable.ic_check), contentDescription = null)
}
}
}
private enum class AnimatedArcState {
START,
END
}
@Preview
@Composable
private fun CheckProgressIndicatorPreview() {
Column {
CircularProgressIndicator(progress = 10f)
CircularProgressIndicator(progress = 30f)
CircularProgressIndicator(progress = 50f)
CircularProgressIndicator(progress = 100f)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment