Android Compose create shake animation

Issue

I am trying to make shaking animation of shape in Jetpack Compose. I want to use this animation to show error when user enters invalid Pin code. But all I can find is slide in, slide out animations and some scale animations. Any ideas how I can accomplish this?

Update:
After @Thracian answer. I used code as below, shaking my items horizontally:

fun Modifier.shake(enabled: Boolean, onAnimationFinish: () -> Unit) = composed(
    factory = {
        val distance by animateFloatAsState(
            targetValue = if (enabled) 15f else 0f,
            animationSpec = repeatable(
                iterations = 8,
                animation = tween(durationMillis = 50, easing = LinearEasing),
                repeatMode = RepeatMode.Reverse
            ),
            finishedListener = { onAnimationFinish.invoke() }
        )

        Modifier.graphicsLayer {
            translationX = if (enabled) distance else 0f
        }
    },
    inspectorInfo = debugInspectorInfo {
        name = "shake"
        properties["enabled"] = enabled
    }
)

Solution

enter image description here

Gif is slower than actual animation unfortunately but it gives an idea of outcome.

This can be done in many ways. You should change scaleX or scaleY or both in short time duration to have a shake effect. If you wish to have rotation change rotationZ of Modifier.graphicsLayer either

 @Composable
private fun ShakeAnimationSamples() {
    Column(modifier = Modifier
        .fillMaxSize()
        .padding(10.dp)) {

        var enabled by remember {
            mutableStateOf(false)
        }
        val scale by animateFloatAsState(
            targetValue = if (enabled) .9f else 1f,
            animationSpec = repeatable(
                iterations = 5,
                animation = tween(durationMillis = 50, easing = LinearEasing),
                repeatMode = RepeatMode.Reverse
            ),
            finishedListener = {
                enabled = false
            }
        )

        val infiniteTransition = rememberInfiniteTransition()
        val scaleInfinite by infiniteTransition.animateFloat(
            initialValue = 1f,
            targetValue = .85f,
            animationSpec = infiniteRepeatable(
                animation = tween(30, easing = LinearEasing),
                repeatMode = RepeatMode.Reverse
            )
        )

        val rotation by infiniteTransition.animateFloat(
            initialValue = -10f,
            targetValue = 10f,
            animationSpec = infiniteRepeatable(
                animation = tween(30, easing = LinearEasing),
                repeatMode = RepeatMode.Reverse
            )
        )

        Icon(
            imageVector = Icons.Default.NotificationsActive,
            contentDescription = null,
            tint = Color.White,
            modifier = Modifier
                .graphicsLayer {
                    scaleX = if (enabled) scale else 1f
                    scaleY = if (enabled) scale else 1f
                }
                .background(Color.Red, CircleShape)
                .size(50.dp)
                .padding(10.dp)
        )

        Icon(
            imageVector = Icons.Default.NotificationsActive,
            contentDescription = null,
            tint = Color.White,
            modifier = Modifier
                .graphicsLayer {
                    scaleX = scaleInfinite
                    scaleY = scaleInfinite
                    rotationZ = rotation
                }
                .background(Color.Red, CircleShape)
                .size(50.dp)
                .padding(10.dp)
        )



        Button(onClick = { enabled = !enabled }) {
            Text("Animation enabled: $enabled")
        }

    }
}

Also you can do it as a Modifier either

fun Modifier.shake(enabled: Boolean) = composed(

    factory = {
        
        val scale by animateFloatAsState(
            targetValue = if (enabled) .9f else 1f,
            animationSpec = repeatable(
                iterations = 5,
                animation = tween(durationMillis = 50, easing = LinearEasing),
                repeatMode = RepeatMode.Reverse
            )
        )

        Modifier.graphicsLayer {
            scaleX = if (enabled) scale else 1f
            scaleY = if (enabled) scale else 1f
        }
    },
    inspectorInfo = debugInspectorInfo {
        name = "shake"
        properties["enabled"] = enabled
    }
)

Usage

Icon(
    imageVector = Icons.Default.NotificationsActive,
    contentDescription = null,
    tint = Color.White,
    modifier = Modifier
        .shake(enabled)
        .background(Color.Red, CircleShape)
        .size(50.dp)
        .padding(10.dp)
)

Answered By – Thracian

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published