Touch Feedback Animation like Spotify in Jetpack Compose
Build a Bouncing Click Effect in Jetpack Compose
It’s essential to show the user click feedback bla bla bal, Long story short I like Spotify's bouncing click effect more than the default Ripple effect so let’s do it 🤩
Along the way, we will explore some modifiers and animation too…
To do it we will create a reusable Modifier
(similar to the clickable modifier) that decreases the scale and opacity when the UI element is pressed.
I will go ahead and put all the code and then explain it…
/**
* Adds a bouncing click effect to a Composable.
*
* @param enabled whether the UI element should be enabled and clickable
* @param onClick the action to perform when the UI element is clicked
*/
fun Modifier.bouncingClickable(
enabled: Boolean = true,
onClick: () -> Unit
) = composed {
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
val animationTransition = updateTransition(isPressed, label = "BouncingClickableTransition")
val scaleFactor by animationTransition.animateFloat(
targetValueByState = { pressed -> if (pressed) 0.94f else 1f },
label = "BouncingClickableScaleFactorTransition",
)
val opacity by animationTransition.animateFloat(
targetValueByState = { pressed -> if (pressed) 0.7f else 1f },
label = "BouncingClickableOpacityTransition"
)
this
.graphicsLayer {
this.scaleX = scaleFactor
this.scaleY = scaleFactor
this.alpha = opacity
}
.clickable(
interactionSource = interactionSource,
indication = null,
enabled = enabled,
onClick = onClick
)
}
- We’re using an
interactionSource
to detect if the composable is pressed usingcollectIsPressedAsState()
function. - Then since we have multiple animations that we want to run when
isPressed == true
, we useupdateTransition()
that provides us with extension functions to run multiple animations. - To create the animations, we use
animateFloat()
extension to create two animations, one forscaleFactor
and one foropacity
that we will use to animate our UI element. - To change the scale & alpha we used
graphicsLayer
Modifier which is usually used to apply transformations and animations to a UI element. (check out this great article to learn more) - Finally, we add a clickable modifier to handle click and remove the default ripple effect by setting
indication
param tonull
Note: You can play with the
scaleFactor
&opacity
values to make it more suitable for your use-case.
That’s it, let’s use it 🤩
Box(
modifier = Modifier
.bouncingClickable {
println("Clicked...")
}
.size(100.dp)
.clip(CircleShape)
.background(Color.Black)
) {}
And in case you wanna make it more bouncy, we can add a spring
animation spec to our scaleFactor
val scaleFactor by animationTransition.animateFloat(
targetValueByState = { pressed -> if (pressed) 0.94f else 1f },
label = "BouncingClickableScaleFactorTransition",
transitionSpec = {
spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
}
)
It doesn’t look very different in the gif but believe me it looks better :)
That’s it, I hope you loved the blog ❤️