Creating a wiggle animation in jetpack compose

·

3 min read

Indicating that the user has entered the wrong input, or folk has to complete the steps on the particular screen before moving to the next step is sometimes a trivial step. This indication is sometimes provided through haptic feedback or wiggle animation. image.png Let us see how we can achieve the same in Jetpack compose.

Let's start by creating a Text to be animated.

@Composable
fun Greeting(name: String) {

    Text(text = "Hello $name!")

    Button(){
        Text(text = "Animate")
    }
}

Before we move any further, let's take a second to understand how this will work. We will provide an offset to the text, and change the offset of the text on the x-axis during the animation duration. This brings us to the prerequisites that we need to have a variable offset, something to change the offset.

For having a variable offset we will use Animatable like following

val offsetX = remember { Animatable(0f) }

Text(
    text = "Hello $name!",
    modifier = Modifier
        .offset(offsetX.value.dp, 0.dp)
)

We will then create an AnimationSpec using keyframes like following

private val shakeKeyframes: AnimationSpec<Float> = keyframes {
    durationMillis = 800
    val easing = FastOutLinearInEasing

    // generate 8 keyframes
    for (i in 1..8) {
        val x = when (i % 3) {
            0 -> 4f
            1 -> -4f
            else -> 0f
        }
        x at durationMillis / 10 * i with easing
    }
}

Let us understand how the above snippet works. We first define the duration of the entire animation using durationMillis property, and we then define the Animation Curve, for this example, I am using the FastOutLinearInEasing Curve, but if required you can use CubicBezierEasing . You can easily generate one from Cubic-Bezier. We then define the different values of the Animation Spec. Breaking down the above for loop it would look something like this.

-4F at durationMillis / 10 * 1 with easing
0F at durationMillis / 10 * 2 with easing
4F at durationMillis / 10 * 3 with easing
-4F at durationMillis / 10 * 4 with easing
0F at durationMillis / 10 * 5 with easing
4F at durationMillis / 10 * 6 with easing
0F at durationMillis / 10 * 7 with easing
-4F at durationMillis / 10 * 8 with easing

Boom!! We have our prerequisites in place. We just need to apply the offset to our text, and then animate our offsetX. We can do so in the following way. Apply offset like the following. Note that we are only using the x value of offset since we need to animate it horizontally.

Text(
    text = "Hello $name!",
    modifier = Modifier
        .offset(offsetX.value.dp, 0.dp)
)

and then create a CoroutineScope so that we can animate offsetX .

coroutineScope.launch {
    offsetx.animateTo(
        targetValue = 0f,
        animationSpec = shakeKeyframes,
    )
}

And since we intended to show this to warn users, let's also add haptic in it.

Final GitHub gist with haptic feedback added.


Like to say "thank you" and to help others find this article.

You can Follow me or reach out to me on Twitter for any queries.

You can also buy me a coffee : )

Thank you!