What You Will Build
A layered water wave animation where three sine wave layers undulate continuously against a deep blue background. This effect is used in weather apps, hydration trackers, and meditation interfaces to create calming ambient motion.
Why Sine Wave Animation Teaches Important Concepts
Sine waves are the foundation of most organic-looking animations. By combining rememberInfiniteTransition with trigonometric math on Canvas, you learn the pattern for any continuous, smooth, looping animation: water, audio visualizers, loading indicators, and heartbeat monitors.
Key Compose Concepts
- rememberInfiniteTransition for animations that loop forever without user interaction.
- Canvas with Path for drawing complex custom shapes.
- Phase shifting to create the illusion of wave movement by offsetting the sine function over time.
Step 1: Set Up the Infinite Phase Animation
val infiniteTransition = rememberInfiniteTransition(label = "wave")
val phase by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 2f * Math.PI.toFloat(),
animationSpec = infiniteRepeatable(
animation = tween(3000, easing = LinearEasing)
),
label = "phase"
)
Step 2: Draw Layered Waves on Canvas
Three wave layers with different amplitudes and alpha values create depth. Each layer is a filled Path that follows a sine curve:
Canvas(Modifier.fillMaxSize()) {
val w = size.width
val h = size.height
for (layer in 0..2) {
val path = Path()
path.moveTo(0f, h * 0.6f)
var x = 0f
while (x <= w) {
val y = h * 0.6f + sin(
(x / w * 4 * Math.PI + phase + layer).toDouble()
).toFloat() * 30f * (layer + 1)
path.lineTo(x, y)
x += 5f
}
path.lineTo(w, h)
path.lineTo(0f, h)
path.close()
drawPath(
path,
color = Color(0xFF42A5F5)
.copy(alpha = 0.3f - layer * 0.08f)
)
}
}
Tips and Pitfalls
- Layer offset by index: Adding
+ layerto the sine phase shifts each wave layer, creating parallax depth. - Amplitude scaling:
30f * (layer + 1)makes deeper layers taller, reinforcing the depth illusion. - LinearEasing is mandatory for smooth continuous waves. Any other easing creates visible speed changes at loop boundaries.
- Step size of 5f balances smoothness and performance. Smaller values create smoother curves but more path segments.
- Path.close() is essential to fill the area under the wave down to the bottom of the screen.
Minimum SDK: API 24+ with Compose BOM 2024.01+