· 3 min read Posted by Júlia Jakubcová

Loading Shimmer in Compose

Compose provides circular and linear progress indicators. However, lately it’s popular to show gray skeletons of the views that are being loaded with a shimmering animation on them. It’s a nice touch that can make your applications look a bit more refined. Here is how I created this effect with Compose.

Compose provides circular and linear progress indicators. However, lately, it’s popular to show gray skeletons of the views that are being loaded with a shimmering animation on them. It’s a nice touch that can make your applications look a bit more refined. Here is how I created this effect with Compose.

Dogify loading Dogify loaded

First create views that will look like outlines of the views that will show up when the loading finishes. You can use rectangles with rounded corners for each text, image or other component. You can create a rounded rectangle view in compose like this:

Box(
    modifier = Modifier
        .size(width = 64.dp, height = 24.dp)
        .background(Color.LightGray, RoundedCornerShape(4.dp))
)

Now we need to create an animation for shimmering. Create a transition and a float animation that will move the “light beam” across the view. The 400f constant is the end position of the translation.

val transition = rememberInfiniteTransition()

val translateAnimation by transition.animateFloat(
    initialValue = 0f,
    targetValue = 400f,
    animationSpec = infiniteRepeatable(
        tween(durationMillis = 1500, easing = LinearOutSlowInEasing),
        RepeatMode.Restart
    ),
)

Next prepare colors and create a brush with the translateAnimation used for offsets. In this case we used 100f as a constant for the “beam” width and used mirror mode for it to fade to the darker gray on both sides symmetrically. If you want the beam to be horizontal or vertical instead of diagonal, only use a constant in the x or y parameter of the Offset.

val shimmerColors = listOf(
    Color.LightGray.copy(alpha = 0.9f),
    Color.LightGray.copy(alpha = 0.4f),
)
val brush = Brush.linearGradient(
    colors = shimmerColors,
    start = Offset(translateAnimation, translateAnimation),
    end = Offset(translateAnimation + 100f, translateAnimation + 100f),
    tileMode = TileMode.Mirror,
)

Then we just use the brush in the views background.

Box(
    modifier = Modifier
        .size(width = 64.dp, height = 24.dp)
        .background(brush, RoundedCornerShape(4.dp))
)

For easier use in multiple places I created a Modifier extension.

fun Modifier.shimmerBackground(shape: Shape = RectangleShape): Modifier = composed {
    val transition = rememberInfiniteTransition()

    val translateAnimation by transition.animateFloat(
        initialValue = 0f,
        targetValue = 400f,
        animationSpec = infiniteRepeatable(
            tween(durationMillis = 1500, easing = LinearOutSlowInEasing),
            RepeatMode.Restart
        ),
    )
    val shimmerColors = listOf(
        Color.LightGray.copy(alpha = 0.9f),
        Color.LightGray.copy(alpha = 0.4f),
    )
    val brush = Brush.linearGradient(
        colors = shimmerColors,
        start = Offset(translateAnimation, translateAnimation),
        end = Offset(translateAnimation + 100f, translateAnimation + 100f),
        tileMode = TileMode.Mirror,
    )
    return@composed this.then(background(brush, shape))
}

And on a view’s Modifier we just call .shimmerBackground().

Box(
    modifier = Modifier
        .size(64.dp)
        .shimmerBackground(RoundedCornerShape(4.dp))
)

And this is what it looks like.

Shimmer in action

Thanks for checking out this tutorial! If you try this out we’d love to know how it worked for you. You can reach out to us on Twitter or head to our contact us page.