Jetpack Compose – Pulsating circles animation

Jetpack Compose is fun, and animations are cool. Animations are a little harder to get your heads around. But, once you have got hold of the basics, implementing an animation is pretty easy with Jetpack Compose. After going through the animation APIs and examples, I tried to implement a pulsating circular text container. Here’s what I had in mind.

This is a recording from the Android Studio’s “Interactive mode”. So, the quality of the video isn’t great. But, when you run this composable, you wouldn’t see the pixelation.

Below is a snapshot of how the non-animating view of the composable.

And here are the building blocks.

First, I created a circle shape composable that we can re-use.

SimpleCircleShape2

@Composable
fun SimpleCircleShape2(
    size: Dp,
    color: Color = Color.White,
    borderWidth: Dp = 0.dp,
    borderColor: Color = Color.LightGray.copy(alpha = 0.0f)
) {
    Column(
        modifier = Modifier
            .wrapContentSize(Alignment.Center)
    ) {
        Box(
            modifier = Modifier
                .size(size)
                .clip(CircleShape)
                .background(
                    color
                )
                .border(borderWidth, borderColor)
        )
    }
}

Then, I have used a “Box” composable to layer the circles and the text on top of each other.

PulsatingCircles

@Composable
fun PulsatingCircles(text: String) {
    Column {
        val infiniteTransition = rememberInfiniteTransition()

        val size by infiniteTransition.animateValue(
            initialValue = 200.dp,
            targetValue = 190.dp,
            Dp.VectorConverter,
            animationSpec = infiniteRepeatable(
                animation = tween(500, easing = FastOutLinearInEasing),
                repeatMode = RepeatMode.Reverse
            )
        )

        val smallCircle by infiniteTransition.animateValue(
            initialValue = 150.dp,
            targetValue = 160.dp,
            Dp.VectorConverter,
            animationSpec = infiniteRepeatable(
                animation = tween(1000, easing = FastOutLinearInEasing),
                repeatMode = RepeatMode.Reverse
            )
        )

        Box(
            modifier = Modifier
                .fillMaxWidth()
                .height(200.dp),
            contentAlignment = Alignment.Center
        ) {
            SimpleCircleShape2(
                size = size,
                color = MaterialTheme.colors.primary.copy(alpha = 0.25f)
            )
            SimpleCircleShape2(
                size = smallCircle,
                color = MaterialTheme.colors.primary.copy(alpha = 0.25f)
            )
            SimpleCircleShape2(
                size = 130.dp,
                color = MaterialTheme.colors.onPrimary
            )
            Column {
                Row(verticalAlignment = Alignment.CenterVertically) {
                    Text(
                        text = text,
                        style = TextStyle().copy(color = MaterialTheme.colors.primary)
                    )
                }
            }
        }
    }
}

Here, I am animating the sizes of the outer 2 circular shapes. So, there are 2 animations running simultaneously. I have also created a preview for the composable which helped in implementing and simulating as well without actually running in on the phone.

PreviewPulsatingCircles

@Preview
@Composable
fun PreviewPulsatingCircles() {
    PulsatingCircles("Compose")
}

That’s all.