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
We are what we repeatedly do. Excellence, then, is not an act, but a habit. Try out Justly and start building your habits today!
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…
DampingRatioNoBouncy
Spring.StiffnessMedium
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.
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
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
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))
}
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.
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!! 😈