Jetpack Compose Surface click ripple is not clipped based on shape?

Viewed 10142

I have 3 Surfaces as can be seen in gif when i click ripple effect propagates without taking the shapes of Surfaces into consideration.

enter image description here

Which are created with

@Composable
fun SurfaceClickPropagationExample() {

    // Provides a Context that can be used by Android applications
    val context = AmbientContext.current

    //  Offset moves a component in x and y axes which can be either positive or negative
    //  When a component inside surface is offset from original position it gets clipped.
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .wrapContentHeight()
            .clipToBounds()
            .clickable(onClick = {})
    ) {

        Surface(
            modifier = Modifier
                .preferredSize(150.dp)
                .padding(12.dp)
                .clickable(onClick = {
                })
                .clipToBounds(),
            elevation = 10.dp,
            shape = RoundedCornerShape(10.dp),
            color = (Color(0xFFFDD835))
        ) {

            Surface(
                modifier = Modifier
                    .preferredSize(80.dp)
                    .clipToBounds()
                    .offset(x = 50.dp, y = (-20).dp)
                    .clickable(onClick = {
                    }),
                elevation = 12.dp,
                shape = CircleShape,
                color = (Color(0xFF26C6DA))
            ) {

            }
        }

        Surface(
            modifier = Modifier
                .preferredSize(110.dp)
                .padding(12.dp)
                .offset(x = 110.dp, y = 20.dp)
                .clickable(onClick = {}),
            shape = CutCornerShape(10.dp),
            color = (Color(0xFFF4511E)),
            elevation = 8.dp
        ) {}
    }
}

I added Modifier.clipToBounds() to check if it works with it, but it does not work with or without it.

3 Answers

Update for compose version 1.0.0-beta08:

Use the new experimental overload of Surface that accepts onClick.

@ExperimentalMaterialApi
@Composable
fun Surface(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    shape: Shape = RectangleShape,
    color: Color = MaterialTheme.colors.surface,
    contentColor: Color = contentColorFor(color),
    border: BorderStroke? = null,
    elevation: Dp = 0.dp,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    indication: Indication? = LocalIndication.current,
    enabled: Boolean = true,
    onClickLabel: String? = null,
    role: Role? = null,
    content: () -> Unit
): @ExperimentalMaterialApi @Composable Unit

Documentation: https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#Surface(kotlin.Function0,androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Shape,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,androidx.compose.foundation.BorderStroke,androidx.compose.ui.unit.Dp,androidx.compose.foundation.interaction.MutableInteractionSource,androidx.compose.foundation.Indication,kotlin.Boolean,kotlin.String,androidx.compose.ui.semantics.Role,kotlin.Function0)


Try applying Modifier.clip(shape: Shape) before Modifier.clickable.

When using Modifiers in compose, the order matters. Modifier elements that appear first will be applied first. (documentation)

I was writing this answer when both Surface and Card layouts with the onClick parameter are experimental.

If you don't want to use experimental components, you can try wrapping your view inside a Button component like this:

    Button(
        onClick = { /* TODO to handle */ },
        shape = /* your shape */,
        colors = ButtonDefaults.buttonColors(backgroundColor = /* your color */),
        elevation = elevation(defaultElevation = 0.dp, pressedElevation = 0.dp)
    ) {
        /* Here you can paste your parent layout like Box, Column, or Row,
 but set max size and horizontal alignment to override the default button's center horizontal alignment */
        Column(
            modifier = Modifier.fillMaxSize(),
            horizontalAlignment = Alignment.Start
        ) {
            // other stuff there
        }
    }

Here is the result:

Ripple effect on click

I'm not too fond of the ripple effect of Surface.

You can also clip ripple effect while using clickable modifier:

Row(modifier = Modifier.clickable(
    interactionSource = remember { MutableInteractionSource() },
    indication = rememberRipple(bounded = true),
) {
    onClick(filter)
}) 
Related