Android — Spring & Fling Animations with Jetpack compose

Exploring Compose animations API to create physics-based cool animations.
Jun 15 2022 · 4 min read

Background

With Jetpack compose, you can give your app a smooth look through various animations.

This blog post demonstrates how we can add physics-based animation such as fling and spring animation in Jetpack Compose.

What we’re going to implement

  1. Spring release animation
  2. Fling animation
  3. Chain spring animations

We are what we repeatedly do. Excellence, then, is not an act, but a habit. Try out Justly and start building your habits today!

Let’s get started!

But first, let’s get familiar with spring animation. Spring is physics-based animation, unlike other animations, it adds a more natural and smooth effect in motion.

In Jetpack compose, we have spring AnimationSpec to customise animations. SpringSpec takes three parameters…

  1. dampingRatio — define the bouncy of spring. The lower the damping ratio, the higher the bounciness. The default value is DampingRatioNoBouncy
  2. stiffness — is something like animation duration, it defines the speed of spring to the final position. The default value is Spring.StiffnessMedium
  3. visibilityThreshold — specifies the visibility threshold.

Spring release animation

In this example, we’ll have a vertical draggable Circle, when we release the drag, the circle goes back to its initial position.


@Composable
fun SpringRelease(){
    Box(
        modifier = Modifier
            .fillMaxSize()
            .padding(20.dp),
        contentAlignment = Alignment.TopCenter
    ) {
        Circle(modifier = Modifier
            .background(ThemeColor, CircleShape))
    }
}
@Composable
fun Circle(modifier: Modifier) {
    Box(
        modifier = modifier
            .size(50.dp),
        contentAlignment = Alignment.Center
    ) {
        Icon(Icons.Default.Star, contentDescription = null, tint = Color.White)
    }
}

Now, let's enable our circle to drag vertically. First, we need to detect touch down event on Circle.


Modifier
        .pointerInput(Unit) {
            forEachGesture {
                awaitPointerEventScope {
                    //Detect a touch down event
                    awaitFirstDown()
                    do {
                        val event: PointerEvent = awaitPointerEvent()
                        event.changes.forEach { pointerInputChange: PointerInputChange ->
                            //Consume the change
                        }
                    } while (event.changes.any { it.pressed })

                    // Touch released - ACTION_UP
                }
            }
        }
  • awaitPointerEventScope — Suspend and install a pointer input block that can await input events and respond to them immediately.
  • awaitFirstDown — Reads events until the first down is received.
  • awaitPointerEvent — Suspend until a PointerEvent is reported.

Now let’s update offset to move Circle on drag.


val offsetY = remember { Animatable(0f) }
val scope = rememberCoroutineScope()
Circle(modifier = Modifier
    .offset { IntOffset(0, offsetY.value.roundToInt()) }
   
    .pointerInput(Unit) {
        ....
        //Detect a touch down event
        do {
            val event: PointerEvent = awaitPointerEvent()
            event.changes.forEach { pointerInputChange: PointerInputChange ->
                //Consume the change
                scope.launch {
                    offsetY.snapTo(
                        offsetY.value + pointerInputChange.positionChange().y
                    )
                }
            }
        } while (event.changes.any { it.pressed })
        // Touch released - Action_UP
    }
)

So here, we’ve consumed the pointer input event and updated offsetY without animating it with snapTo as we don’t want any animation while dragging circle.

Now let’s implement final step to move Circle back to initial position.


// Touch released - Action_UP
scope.launch {
    offsetY.animateTo(
        targetValue = 0f
    )
}

Nothing fancy, we just set targetValue to initial position. Let’s check output now.

Chain spring.png

Okay, Let’s make it more realistic by adding bounce effect when Circle reach at initial position.


scope.launch {
    offsetY.animateTo(
        targetValue = 0f, spring(
            dampingRatio = Spring.DampingRatioLowBouncy,
            stiffness = StiffnessLow

        )
    )
}

With DampingRatioLowBouncy & StiffnessLow Let’s check the output

Cool !! Full source code available on Github

2. Fling animation

To move view with target position is quite easy, but to animate view with dynamic value is bit tough, for ex, to move view on touch pointer.

In this example we’re going to implement the same with Animatable and SpringSpec

Let’s first detect the touch pointer position with pointerInput Modifier.


val position = awaitPointerEventScope {
    awaitFirstDown().position
}

This will wait unit event happens and on first down touch event it resume the suspended coroutine and we have position of pointer. Now we just have to animate offset with the pointer position.


launch {
    offset.animateTo(
        position, spring(
            dampingRatio = Spring.DampingRatioLowBouncy,
            stiffness = StiffnessVeryLow
        )
    )
}

Let’s set this offset to our Circle modifier.


val offset = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) }
Circle(modifier = Modifier
    .offset { offset.value.toIntOffset() })

And the output…

Full Source code of this example available on Github

3. Chain spring animations

In this example we’re going to implement animation that looks like our views are connected like spring.


Column(
    modifier = Modifier
        .fillMaxSize()
        .padding(20.dp),
    verticalArrangement = Arrangement.Center,
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Circle(modifier = Modifier
        .background(ThemeColor, CircleShape))

    Spacer(modifier = Modifier.height(30.dp))
    Circle(modifier = Modifier
        .background(Color.White.copy(0.8f), CircleShape))

    Spacer(modifier = Modifier.height(30.dp))
    Circle(modifier = Modifier
        .background(Color.White.copy(0.3f), CircleShape))
}
Chain spring.png

Let’s make these circles movable.

val offset = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) }
Circle(modifier = Modifier
    .offset { offset.value.toIntOffset() }
    .pointerInput(Unit) {
        detectDragGestures { change, dragAmount ->
            change.consumeAllChanges()
            scope.launch {
                offset.animateTo(
                    offset.value + change.position, spring(
                        dampingRatio = Spring.DampingRatioLowBouncy
                    )
                )
            }
        }
    })

This makes our first circle draggable, like this...

Now, it’s time to chain other two circle with our first draggable circle.


scope.launch {
    offset2.animateTo(
        offset.value + change.position, spring(
            dampingRatio = Spring.DampingRatioLowBouncy,
            stiffness = 100f
        )
    )
}

scope.launch {
    offset3.animateTo(
        offset.value + change.position, spring(
            dampingRatio = Spring.DampingRatioLowBouncy,
            stiffness = StiffnessVeryLow
        )
    )
}
Circle(modifier = Modifier
    .offset { offset2.value.toIntOffset())

Circle(modifier = Modifier
    .offset { offset3.value.toIntOffset() })

To make Circles connected with each other and to have effect like 2nd and 3rd Circles are following 1st circle we’ve setup different damping ratio and stiffness. Let’s see the result.

Nice chain!! Full source code of this example is available on Github.

That’s it.

Conclusion 

We’ve implemented really cool animation with springSpec, but that’s just a tiny part of Jetpack compose animation API, as it provides various different ways to make your app smooth and attractive with animations.

Here’s some cool progress/loader animation in Jetpack compose worth having a look at.

That’s it for today. Keep Animating!! 😈


Code, Build, Repeat.
Stay updated with the latest trends and tutorials in Android, iOS, and web development.
radhika-s image
Radhika saliya
Mobile App Developer | Sharing knowledge of Jetpack Compose & android development
radhika-s image
Radhika saliya
Mobile App Developer | Sharing knowledge of Jetpack Compose & android development
canopas-logo
We build products that customers can't help but love!
Get in touch

Talk to an expert
get intouch
Our team is happy to answer your questions. Fill out the form and we’ll get back to you as soon as possible
footer
Follow us on
2024 Canopas Software LLP. All rights reserved.