Javier
Javier

Reputation: 1685

Animate shape using Jetpack compose for Android

I want to build a great lockscreen using compose like stock Android 12 like this:

I managed to do something similar, however i have 2 issues, one is when I use pointerInteropFilter to get the action events I use a remember value to change the shape, returning true in this function I got a similar animation, however the click listener is not called, if I return false the shape stays in "square", What I'm missing? is any way to animate the "shape"? I found for DP, color and size but not for shapes. here is the code

@Composable
fun RoundedPinBackground(
    modifier: Modifier = Modifier,
    backgroundColor: Color,
    onClicked: () -> Unit,
    content: @Composable () -> Unit,
) {
    var selected by remember { mutableStateOf(false) }

    val shape = if (selected) {
        RoundedCornerShape(10.dp)
    } else {
        CircleShape
    }
    Surface(
        tonalElevation = 10.dp,
        modifier = Modifier
            .clip(shape)
    ) {
        Box(modifier = modifier
            .size(80.dp)
            .clip(shape)
            .background(color = backgroundColor)
            .clickable { onClicked.invoke() }
            .pointerInteropFilter {
                when (it.action) {
                    MotionEvent.ACTION_DOWN -> {
                        selected = true
                    }

                    MotionEvent.ACTION_UP -> {
                        selected = false
                    }
                }
                true
            },
            contentAlignment = Alignment.Center
        ) {
            content()
        }
    }
}

This are my results

Syntax Description
Motion event true Motion event false
Motion event returns true but no ripple or click Motion event returns false but stays in square shape

Upvotes: 6

Views: 5013

Answers (1)

riggaroo
riggaroo

Reputation: 2982

In order to animate the radius you need to use one of the animate*AsState() APIs.

They key here would be to change the corner radius of your Shape progressively by using animateDpAsState(), as a Circle is a RoundedCornerShape with the corner radius as half the size of the overall Circle.

In order to get the ripple, you can use the clickable modifier with rememberRipple() as the indication.

Below is a working example of both:

@Composable
fun RoundedPinBackground(
    modifier: Modifier = Modifier,
    size: Dp,
    backgroundColor: Color,
    onClicked: () -> Unit,
    content: @Composable () -> Unit,
) {
    val interactionSource = remember { MutableInteractionSource() }
    val isPressed = interactionSource.collectIsPressedAsState()
    val radius = if (isPressed.value) {
        10.dp
    } else {
        size / 2f
    }
    val cornerRadius = animateDpAsState(targetValue = radius)

    Surface(
        tonalElevation = 10.dp,
        modifier = modifier
            .clip(RoundedCornerShape(cornerRadius.value))
    ) {
        Box(
            modifier = Modifier
                .background(color = backgroundColor)
                .size(size)
                .clip(RoundedCornerShape(cornerRadius.value))
                .clickable(
                    interactionSource = interactionSource,
                    indication = rememberRipple()
                ) { onClicked.invoke() },
            contentAlignment = Alignment.Center
        ) {
            content()
        }
    }
}

Animation with radius and ripple

Upvotes: 11

Related Questions