You can use PathMeasure
and with getPathSegments you can get a segmented path based on current progress and draw RoundedRectangle inside DrawScope with a draw Modifier.
@Preview
@Composable
fun DrawProgressTest() {
val pathMeasure by remember { mutableStateOf(PathMeasure()) }
var progress by remember {
mutableStateOf(0f)
}
val path = remember {
Path()
}
val pathWithProgress by remember {
mutableStateOf(Path())
}
Column(
modifier = Modifier.fillMaxSize().padding(16.dp)
) {
Text("Progress: ${progress.toInt()}")
Slider(
value = progress,
onValueChange = {
progress = it
},
valueRange = 0f..100f
)
Icon(
modifier = Modifier.size(64.dp).drawBehind {
if (path.isEmpty) {
path.addRoundRect(
RoundRect(
Rect(offset = Offset.Zero, size),
cornerRadius = CornerRadius(16.dp.toPx(), 16.dp.toPx())
)
)
pathMeasure.setPath(path, forceClosed = false)
}
pathWithProgress.reset()
pathMeasure.setPath(path, forceClosed = false)
pathMeasure.getSegment(
startDistance = 0f,
stopDistance = pathMeasure.length * progress / 100f,
pathWithProgress,
startWithMoveTo = true
)
drawPath(
path = path,
style = Stroke(
4.dp.toPx()
),
color = Color.Black
)
drawPath(
path = pathWithProgress,
style = Stroke(
4.dp.toPx()
),
color = Color.Blue
)
},
tint = if (progress == 100f) Color.Blue else Color.Black,
imageVector = Icons.Default.CarRental,
contentDescription = null
)
}
}
If you want segment to fill from another direction you can use rotate
or transform
inside DrawScope.
And if you want it to animate between big progress changes you can use Animatable
to animate between these values instead of instant change.
@Preview
@Composable
fun DrawProgressTest() {
val pathMeasure by remember { mutableStateOf(PathMeasure()) }
val path = remember {
Path()
}
val pathWithProgress by remember {
mutableStateOf(Path())
}
val animatable = remember {
Animatable(0f)
}
val coroutineScope = rememberCoroutineScope()
Column(
modifier = Modifier.fillMaxSize().padding(16.dp)
) {
Text("Progress: ${animatable.value.toInt()}")
Slider(
value = animatable.value,
onValueChange = {
coroutineScope.launch {
animatable.animateTo(it)
}
},
valueRange = 0f..100f
)
Icon(
modifier = Modifier.size(128.dp)
.drawBehind {
if (path.isEmpty) {
path.addRoundRect(
RoundRect(
Rect(offset = Offset.Zero, size),
cornerRadius = CornerRadius(16.dp.toPx(), 16.dp.toPx())
)
)
pathMeasure.setPath(path, forceClosed = false)
}
pathWithProgress.reset()
pathMeasure.setPath(path, forceClosed = false)
pathMeasure.getSegment(
startDistance = 0f,
stopDistance = pathMeasure.length * animatable.value / 100f,
pathWithProgress,
startWithMoveTo = true
)
drawPath(
path = path,
style = Stroke(
4.dp.toPx()
),
color = Color.Black
)
drawPath(
path = pathWithProgress,
style = Stroke(
4.dp.toPx()
),
color = Color.Blue
)
},
tint = if (animatable.value == 100f) Color.Blue else Color.Black,
imageVector = Icons.Default.CarRental,
contentDescription = null
)
}
}
Also you can refer this answer how you can animate it with different time intervals.
https://mcmap.net/q/1686447/-rectangle-border-progress-bar