What You Will Build

Three colored circles that orbit around a center point with different phases using infinite animations, creating a metaball-style visual effect. The circles overlap and move in opposing directions, creating an organic, lava-lamp-like motion.

Why This Pattern Matters

Metaball effects are used in loading screens, music visualizers, and creative app backgrounds. Building this teaches you rememberInfiniteTransition with multiple independent animation tracks and Canvas circle drawing. For a true metaball merge effect, you would add a blur layer — this creates the foundational orbiting pattern.

Step 1: Define Infinite Transition Animations

@Composable
fun MetaballEffectScreen() {
    val infiniteTransition = rememberInfiniteTransition(
        label = "meta"
    )

    val offsetX by infiniteTransition.animateFloat(
        initialValue = -60f,
        targetValue = 60f,
        animationSpec = infiniteRepeatable(
            animation = tween(
                2000,
                easing = FastOutSlowInEasing
            ),
            repeatMode = RepeatMode.Reverse
        ),
        label = "x"
    )

    val offsetY by infiniteTransition.animateFloat(
        initialValue = -40f,
        targetValue = 40f,
        animationSpec = infiniteRepeatable(
            animation = tween(1500),
            repeatMode = RepeatMode.Reverse
        ),
        label = "y"
    )

Step 2: Draw Orbiting Circles

Each circle uses a different combination of offsetX and offsetY (including negated values) to create orbits that cross each other:

Box(
    modifier = Modifier
        .fillMaxSize()
        .background(Color(0xFF1A1A2E)),
    contentAlignment = Alignment.Center
) {
    Canvas(modifier = Modifier.size(300.dp)) {
        // Purple circle - moves with offset
        drawCircle(
            color = Color(0xFF6C63FF),
            radius = 60.dp.toPx(),
            center = center + Offset(offsetX, offsetY)
        )
        // Pink circle - moves opposite
        drawCircle(
            color = Color(0xFFFF6584),
            radius = 50.dp.toPx(),
            center = center + Offset(-offsetX, -offsetY)
        )
        // Teal circle - crossed axes
        drawCircle(
            color = Color(0xFF00BFA6),
            radius = 40.dp.toPx(),
            center = center + Offset(offsetY, offsetX)
        )
    }
}

Step 3: Add True Metaball Merging (Advanced)

For the circles to actually merge like liquid, apply a blur and threshold filter:

// For true metaball merging on API 31+:
Canvas(
    modifier = Modifier
        .size(300.dp)
        .graphicsLayer {
            // Render to offscreen layer
            renderEffect = RenderEffect
                .createBlurEffect(30f, 30f, Shader.TileMode.DECAL)
                .asComposeRenderEffect()
        }
) {
    // Draw circles with BlendMode
    drawCircle(
        color = Color(0xFF6C63FF),
        radius = 60.dp.toPx(),
        center = center + Offset(offsetX, offsetY),
        blendMode = BlendMode.SrcOver
    )
    // Additional circles...
}

Tips and Pitfalls

  • Different durations create complexity: The X animation runs at 2000ms and Y at 1500ms. Because they are not multiples, the combined path never repeats exactly, creating organic motion.
  • FastOutSlowInEasing: Creates acceleration/deceleration at the ends of each swing, making the motion feel physical rather than mechanical.
  • True metaball effect: Requires RenderEffect blur (API 31+) or a custom shader. The basic version shown here creates the visual pattern without the merging.
  • Color blending: The circles naturally overlap with their default alpha. Increase alpha or use BlendMode.Screen for brighter intersections.

Min SDK: API 21+ for basic, API 31+ for RenderEffect blur

Get New Tutorials by Email

No spam. Just clear, practical breakdowns you can apply right away.

Enjoy this tutorial?

Get new practical tech tutorials in your inbox.