← Back to list

Creating a Smooth WhatsApp-Style Bottom Navigation Animation with Jetpack Compose

One of the things that most impact the perception of quality of a mobile app is micro-interactions. Recently, I decided to replicate the…

Ktlevelll · 2026-05-12 12:47 · 2 claps · 1.5 min read
#jetpack-compose-animation #compose-multiplatform
Open on Medium ↗
Wiki topics: GEN · Genomics & Sequencing 📱 · Mobile Development 🎬 · Film & Television 👗 · Fashion

Creating a Smooth WhatsApp-Style Bottom Navigation Animation with Jetpack Compose

One of the things that most impact the perception of quality of a mobile app is micro-interactions. Recently, I decided to replicate the famous soft “jump” of WhatsApp’s bottom bar icons in my project.

The Goal

  • When the user switches tabs, the icon smoothly lifts upward.
  • It has a subtle overshoot (bounce).
  • Returns to its original position naturally.
  • The animation should only trigger on real tab changes.

Technical Solution

I used what I consider one of the best current approaches in Compose:

  1. Intelligent Animation Trigger
var trigger by remember { mutableStateOf(0) }

LaunchedEffect(isSelected) {
    if (isSelected) trigger++
}
  1. Natural Spring Animations
val jump by animateFloatAsState(
    targetValue = if (isSelected) -11f else 0f,
    animationSpec = spring(dampingRatio = 0.64f, stiffness = 340f)
)
  1. Efficient Rendering with graphicsLayer
Modifier.graphicsLayer {
    translationY = jump
    scaleX = scale * pressScale
    scaleY = scale * pressScale
}
  1. Final Abstraction: Custom Modifier

Created animatedBottomNavIcon() — a reusable Modifier custom that encapsulates all the animation logic, press feedback, and trigger control.

Why graphicsLayer?

  • Doesn’t affect the layout pass (unlike Modifier.offset).
  • Excellent performance for continuous animations.
  • Allows cheap 2D/3D transformations.

This approach delivers extremely fluid navigation without sacrificing performance or causing unnecessary recompositions.

The Result:

The Custom Modifier

@Composable
fun Modifier.animatedBottomNavIcon(
    isSelected: Boolean,
    jumpHeight: Float = -11f,
    selectedScale: Float = 1.18f,
    pressScale: Float = 0.88f
): Modifier = composed {

    var trigger by remember { mutableStateOf(0) }
    LaunchedEffect(isSelected) {

        if (isSelected) trigger++

    }

    val jump by animateFloatAsState(
        targetValue = if (isSelected) jumpHeight else 0f,
        animationSpec = spring(
            dampingRatio = 0.64f,
            stiffness = 340f
        ),
        label = "jump"
    )

    val scale by animateFloatAsState(
        targetValue = if (isSelected) selectedScale else 1f,
        animationSpec = spring(
            dampingRatio = 0.72f,
            stiffness = 320f
        ),
        label = "scale"
    )

    // Press effect
    val interactionSource = remember { MutableInteractionSource() }
    val isPressed by interactionSource.collectIsPressedAsState()

    val press by animateFloatAsState(
        targetValue = if (isPressed) pressScale else 1f,
        animationSpec = spring(stiffness = 600f),
        label = "press"
    )

    this.then(
        Modifier
            .graphicsLayer {
                translationY = jump
                scaleX = scale * press
                scaleY = scale * press

            }
    )

}

메타데이터
post_id
11e55a809446
slug
creating-a-smooth-whatsapp-style-bottom-navigation-animation-with-jetpack-compose-11e55a809446
url
https://medium.com/@ktlevelll/creating-a-smooth-whatsapp-style-bottom-navigation-animation-with-jetpack-compose-11e55a809446
canonical_url
https://medium.com/@ktlevelll/creating-a-smooth-whatsapp-style-bottom-navigation-animation-with-jetpack-compose-11e55a809446
author_url
https://medium.com/@ktlevelll
status
ok
fetched_at
2026-06-09 15:37:30