https://kotlinlang.org logo
Title
k

KotlinLeaner

04/19/2023, 12:59 PM
Hi guys, Is there any similar library for pulse animation in jetpack-compose? Any article how to build in compose?
o

Oleksandr Balan

04/19/2023, 2:15 PM
I would simply draw it on Canvas with a modifier. Somethink like this could be a starting point:
fun Modifier.pulse(
    color: Color = Color.Magenta,
    count: Int = 3,
    duration: Int = 5_000,
    easing: Easing = LinearEasing,
): Modifier = composed {
    val transition = rememberInfiniteTransition()

    val progress = List(count) {
        transition.animateFloat(
            initialValue = 0f,
            targetValue = 1f,
            animationSpec = InfiniteRepeatableSpec(
                animation = tween(
                    durationMillis = duration,
                    easing = easing
                ),
                initialStartOffset = StartOffset(it * duration / count),
                repeatMode = RepeatMode.Restart,
            )
        )
    }

    drawBehind {
        val radius = min(size.width, size.height) / 2f

        progress.forEach {
            drawCircle(
                color = color.copy(alpha = 1f - it.value),
                radius = radius * it.value
            )
        }
    }
}
Usage:
Box(
    contentAlignment = Alignment.Center,
    modifier = Modifier.fillMaxSize()
) {
    Box(
        contentAlignment = Alignment.Center,
        modifier = Modifier
            .size(100.dp)
            .pulse()
    ) {
        Icon(
            imageVector = Icons.Default.AccountCircle,
            contentDescription = null,
        )
    }
}
k

KotlinLeaner

04/19/2023, 2:32 PM
what is the difference between of duration value using
3000
or
3_000
?
o

Oleksandr Balan

04/19/2023, 2:33 PM
They are the same, it is just about readability, see https://kotlinlang.org/docs/numbers.html#literal-constants-for-numbers
k

KotlinLeaner

04/19/2023, 2:34 PM
Above solution is good.
Thanks it great help
What is the purpose of this formula
StartOffset(it * duration / count)
?
o

Oleksandr Balan

04/19/2023, 2:37 PM
In that scope
it
refers to the index of item in the list and this formula is used to delay each “circle”. For example when duration is 3s and there are 3 circles the first one will have delay of 0ms (
0 * 3_000 / 3
), the second circle will start its animation after 1s (
1 * 3_000 / 3
) and the last one will start after 2s (
2 * 3_000 / 3
)
k

KotlinLeaner

04/19/2023, 2:40 PM
ohh nice explanation.. Thanks
Thanks Oleksandr, I completed my solution. I didn't use like modifier. I draw inside the canvas. Can you please look on this solution and any improvements needed?
Preview(showBackground = true)
@Composable
fun PulseAnimation() {
    Box(
        contentAlignment = Alignment.Center,
        modifier = Modifier.fillMaxSize()
    ) {
        val transition = rememberInfiniteTransition()
        val duration = 3_000
        val count = 3
        val progress = List(3) {
            transition.animateFloat(
                initialValue = 0f,
                targetValue = 1f,
                animationSpec = InfiniteRepeatableSpec(
                    animation = tween(durationMillis = duration, easing = LinearEasing),
                    initialStartOffset = StartOffset(it * duration / count),
                    repeatMode = RepeatMode.Restart,
                )
            )
        }
        Canvas(modifier = Modifier.fillMaxHeight()) {
            val radius = 40.dp.toPx() / 2f
            drawLine(
                color = Color.Black,
                start = Offset(x = size.width / 2, y = 0F),
                end = Offset(x = size.width / 2, y = size.height / 2),
                strokeWidth = 2.dp.toPx(),
            )
            progress.forEach {
                drawCircle(
                    color = Color.Magenta.copy(alpha = 1f - it.value),
                    radius = radius * it.value,
                    center = Offset(size.width, size.height / 2)
                )
            }
        }
    }
}
o

Oleksandr Balan

04/19/2023, 3:39 PM
Looks good to me 👍 As a small tip you could replace
Offset(x = size.width / 2, y = size.height / 2)
with simple
center
, it is a property provided in
DrawScope
k

KotlinLeaner

04/19/2023, 3:47 PM
I really appreciate it for helping me.