34

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.

Thracian
  • 43,021
  • 16
  • 133
  • 222
  • That's a cool app, are you programming it on your own? I am very interested in it, as it looks like it is a app for learning compose :)? – Andrew Jan 04 '21 at 09:41
  • 1
    @Andrew yes, it's a tutorial app currently i'm working on, but it's still work under progress, i intend to add other material widgets, state, navigation, animation and more, probably finish it until end of this month. You can check it out [here](https://github.com/SmartToolFactory/Jetpack-Compose-Tutorials) – Thracian Jan 04 '21 at 10:00
  • Thank you very much, I am looking forward! – Andrew Jan 04 '21 at 10:06

4 Answers4

55

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


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)

Edric
  • 24,639
  • 13
  • 81
  • 91
Noah
  • 2,718
  • 3
  • 17
  • 23
  • You are right order of modifiers change how padding and clickable area will have ripple effect but in my question `shape` is a parameter for surface, not for `modifier` but `Modifier.clip`clips the ripple. I think it's overkill to have clip for modifier while `Surface` already has a shape modifier but it's how it's designed so i will use it as intended. – Thracian Jan 04 '21 at 09:46
  • Based on @Noah's answer while having a `RoundedCorner` shape for surface if you clip it with another shape ripple works the way you clip while shape of the Surface is based on it's `shape` parameter. Best thing to do not use shape for Surface and to go with Modifier.clip both for setting a shape and ripple. – Thracian Jan 04 '21 at 09:52
  • 1
    Doing this it clips the shadow as well. I don't think is a recommended general approach but I don't know better than this. – Sotti May 14 '21 at 15:20
3

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),
) {
    // do action
}) 
Javlon
  • 1,108
  • 2
  • 15
  • 29
  • At this moment, the property clickable doesnt have those arguments anymore. The answer above works now. Thanks a lot! – Valney Faria Jun 26 '23 at 02:27
2

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

Patryk Kubiak
  • 1,679
  • 2
  • 11
  • 17
0

I recommend you pass the on click event to child view of Card because is very complicated got shape by child view. If somebody know, wrote the purpose.

This code works when child is clicked and show clipped shadow card (ripple).

PD: I'm using compose.material3

click here ripple card demo gif-ripple

package com.example.jettipapp.ui.widgets

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

val IconButtonSizeModifier = Modifier.size(40.dp)

@Composable
fun RoundIconButton(
    modifier: Modifier,
    imageVector: ImageVector,
    onClick: () -> Unit,
    tint: Color = Color.Black.copy(alpha = 0.8f),
    backgroundColor: Color = MaterialTheme.colorScheme.background,
    elevation: Dp = 4.dp
) {
    Card(
        modifier = modifier
            .padding(all = 4.dp),
        shape = CircleShape,
        elevation = CardDefaults.cardElevation(
            defaultElevation = elevation,
            pressedElevation = elevation
        ),
    ) {
        Column(
            modifier = Modifier
                .clickable {
                    onClick.invoke()
                }
                .then(IconButtonSizeModifier)
                .background(color = backgroundColor)
                .fillMaxSize()
                .clip(CircleShape),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally

        ) {
            Icon(imageVector = imageVector, contentDescription = "Plus or minus icon", tint = tint)
        }
    }
}