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
RenderEffectblur (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.Screenfor brighter intersections.
Min SDK: API 21+ for basic, API 31+ for RenderEffect blur