androidbuttonandroid-jetpack-composebounceandroid-jetpack-compose-gesture

Bounce Button Animation in Compose


I want to make a button like this in Compose: https://pub.dev/packages/flutter_bounceable

But the clickable method is not work in my code.

I tried with this code, but it has an error. Pushing the button, but there's no action. Animations are working well, but not for the clickable.

fun Modifier.bounceClick(onClick: () -> Unit,animationDuration: Int = 100,
                         scaleDown: Float = 0.9f) = composed {
    val interactionSource = MutableInteractionSource()

    val coroutineScope = rememberCoroutineScope()

    val scale = remember {
        Animatable(1f)
    }

    this
        .scale(scale = scale.value)
        .background(
            color = Color(0xFF35898F),
            shape = RoundedCornerShape(size = 12f)
        )
        .clickable(interactionSource = interactionSource, indication = null, onClick = onClick)
        .pointerInput(Unit) {
            while(true)
                awaitPointerEventScope {
                        awaitFirstDown()
                        coroutineScope.launch {
                            scale.animateTo(
                                scaleDown,
                                animationSpec = tween(animationDuration),
                            )
                        }
                        waitForUpOrCancellation()
                        coroutineScope.launch {
                            scale.animateTo(
                                scaleDown,
                                animationSpec = tween(20),
                            )
                            scale.animateTo(
                                1f,
                                animationSpec = tween(animationDuration),
                            )
                    }
            }
        }
}

Solution

  • This is quite simple to do with Compose.

    You should use foreachGesture or awaitEachGesture if Compose version is 1.4.0-alpha03 with Modifier.pointerInput instead of while. Also when you have clickable you don't need Modifier.pointerInput as well , you can use either of them.

    I will only demonstrate how to do it with Modifier.clickable and interactionSource.collectIsPressedAsState() as below.

    Result

    enter image description here

    Implementation

    fun Modifier.bounceClick(
        animationDuration: Int = 100,
        scaleDown: Float = 0.9f,
        onClick: () -> Unit
    ) = composed {
    
        val interactionSource = remember { MutableInteractionSource() }
        val isPressed by interactionSource.collectIsPressedAsState()
    
        val animatable = remember {
            Animatable(1f)
        }
    
        LaunchedEffect(key1 = isPressed) {
            if (isPressed) {
                animatable.animateTo(scaleDown)
            } else animatable.animateTo(1f)
        }
    
        Modifier
            .graphicsLayer {
                val scale = animatable.value
                scaleX = scale
                scaleY = scale
            }
            .clickable(
                interactionSource = interactionSource,
                indication = null
            ) {
                onClick()
            }
    }
    

    Usage

    @Composable
    private fun BounceExample() {
        Row {
            Box(
                Modifier
    
                    .background(Color.Red, RoundedCornerShape(10.dp))
                    .bounceClick {
    
                    }
                    .padding(10.dp),
                contentAlignment = Alignment.Center
            ) {
                Text(text = "Hello World", color = Color.White, fontSize = 20.sp)
            }
            Spacer(modifier = Modifier.width(10.dp))
    
            Box(
                Modifier
    
                    .bounceClick {
    
                    }
                    .background(Color.Green, RoundedCornerShape(10.dp))
                    .padding(10.dp),
                contentAlignment = Alignment.Center
            ) {
                Text(text = "Hello World", color = Color.White, fontSize = 20.sp)
            }
        }
    }