48

In this answer I got wrong ripple animation. Do you know how to create ripple with rounded corners using Jetpack Compose?

With default ripple I have this:
Ripple

Code:

Card(shape = RoundedCornerShape(30.dp),
        border = BorderStroke(width = 2.dp, color = buttonColor(LocalContext.current)),
        backgroundColor = backColor(LocalContext.current),
        modifier = Modifier
            .fillMaxWidth()
            .padding(10.dp)
            .clickable(
                interactionSource = remember { MutableInteractionSource() },
                indication = rememberRipple(radius = 30.dp)
            ) { show = !show }
    ) { ... } //Show is animation of other element.

//If I put radius of ripple 200 dp(it's a height of card) ripple works not normal.

Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
Renattele Renattele
  • 1,626
  • 2
  • 15
  • 32

6 Answers6

57

Starting with M2 1.0.0-beta08 you can solve this issue using the onClick lambda parameter in the Card instead of the clickable modifier:

Card(
    shape = RoundedCornerShape(30.dp),
    modifier = Modifier
        .fillMaxWidth()
        .padding(10.dp),
    onClick = { show = !show }
){
  //card content
} 

If you need the clickable or the combinedClickable modifier you have to use the variant without the onClick parameter and to apply also the clip modifier to the Card using the same Card shape:

val shape = RoundedCornerShape(30.dp)

Card(
    shape = shape,
    modifier = Modifier
        //...height, width, padding
        .clip(shape)
        .combinedClickable(
            onLongClick =  { /** do something */ },
            onClick = { /** do something */ }
        )
){
   //card content
}

With M3 Card you can do the same.


Until 1.0.0-beta07 applying a .clickable modifier to the Card the ripples aren't clipped by the bounds of the layout.

As workaround you can apply the .clickable modifier to the content of the Card (for example a Box):

    Card(
        shape = RoundedCornerShape(30.dp),
        border = BorderStroke(width = 2.dp, color = Color.Blue),
        backgroundColor = Color.White,
        modifier = Modifier
            .fillMaxWidth()
            .padding(10.dp)

    ) {
        Box(Modifier
              .clickable(
                  onClick = { /* ...*/ }
              )
        ){
            Text("Text")
        }
    }
Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
  • If I click on the 1st side of element it works normal but if I click on the 2nd side ripple effect goes over corner. – Renattele Renattele Mar 26 '21 at 16:07
  • @RenatteleRenattele what is 1st side and 2nd side? – Gabriele Mariotti Mar 26 '21 at 16:08
  • Random dots on element. – Renattele Renattele Mar 26 '21 at 16:11
  • @RenatteleRenattele It seems a bug of the Card. The ripples aren't clipped by the bounds of the layout. Posted a workaround. – Gabriele Mariotti Mar 27 '21 at 13:48
  • edit: For anyone reading this in late 2022, the material3 library has the same issue, but has no onClick argument that can be used. The workaround provided with using an interior box seems to work though! – Nathan Nov 25 '22 at 09:14
  • 1
    @Nathan It is not correct. M3 Card API has the onClick [variant](https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#Card(kotlin.Function0,androidx.compose.ui.Modifier,kotlin.Boolean,androidx.compose.ui.graphics.Shape,androidx.compose.material3.CardColors,androidx.compose.material3.CardElevation,androidx.compose.foundation.BorderStroke,androidx.compose.foundation.interaction.MutableInteractionSource,kotlin.Function1)) – Gabriele Mariotti Nov 25 '22 at 09:29
  • I apologize, it looks like it's indeed behind the experimental api, and you can opt in with `@OptIn(ExperimentalMaterial3Api::class)`. I need long press, and it seems like that requires a `combinedClickable` Modifier, however the modifier isn't accepting `onLongClick` without `onClick` as well. I tried it with `onClick` (when the onClick argument is also passed directly to the Card), and it seems like the `combinedClickable -> onClick` argument is just getting discarded. Do you happen to know what the correct approach is here? (edit: if this is out of scope I can make a separate question) – Nathan Nov 25 '22 at 09:39
  • Thanks, that works great! Just fyi it looks like in my case the shape that matches the M3 card default shape has rounded corners of ~`11.dp`, not sure if that's universal or just because of the way I'm using it. – Nathan Nov 25 '22 at 10:42
  • Order is important: set shape, clip to shape, set clickable – Luis Jun 26 '23 at 20:47
29

I've so far identified 2 options:

  1. In addition to setting the shape, use .clip modifier to clip the Card using the same shape:
Card(
    shape = RoundedCornerShape(30.dp),
    modifier = Modifier
        .clip(RoundedCornerShape(30.dp))
        .clickable {
                //do something
        }
) {
    Box {
        Text("Text")
    }
}

The downside of this approach is that the elevation shadow gets clips as well, so your Card loses it's shadow.

  1. Set the .clickable on the Card content composable:
 Card(
    shape = RoundedCornerShape(30.dp)
) {
    Box(
        modifier = Modifier.clickable {
                //do something
        }
    ) {
        Text("Text")
    }
}
Mauro Banze
  • 1,898
  • 15
  • 11
12

I checked the sources of Card/Surface composables and found out that you need to have background and clip modifiers with the same shape. So for example the following Box has rounded corner shape and click ripple is cut with the same bounds:

val shape = RoundedCornerShape(16.dp)
Box(
    modifier = Modifier
        .background(
            color = Color.Yellow,
            shape = shape
        )
        .clip(shape)
        .clickable { onClick() },
) {
 // your content here
}
Oleg Kuzmin
  • 221
  • 2
  • 4
7

Hope this will grant you the easiest solution

Just add .clip(RoundedCornerShape(30.dp)) in the modifier parameter

Here is the full code :

Card(modifier = Modifier
    .padding(30.dp)
    .size(100.dp)
    .clip(RoundedCornerShape(30.dp))
    .clickable {
        // After clip //
    }) { }
skywall
  • 3,956
  • 1
  • 34
  • 52
AI Shakil
  • 1,015
  • 10
  • 20
2

using rememberRipple(bounded = false) will give a circular ripple effect around the clicked component. It can be used as ->

Modifier.clickable(
             indication = rememberRipple(bounded = false),
             interactionSource = remember {
                        MutableInteractionSource()
                    }
            ) { }
                
Divya
  • 145
  • 10
0

When you use long press or other gesture,you can use modifier.indication

val interactionSource = remember { MutableInteractionSource() }

Card(
    modifier = Modifier
      .padding(12.dp, 6.dp)
      .fillMaxWidth()
      .clip(RoundedCornerShape(12.dp))
      .indication(interactionSource, LocalIndication.current)
      .pointerInput(Unit) {
        detectTapGestures(
          onPress = { offset ->
            val press = PressInteraction.Press(offset)
            interactionSource.emit(press)
            tryAwaitRelease()
            interactionSource.emit(PressInteraction.Release(press))
          },
          onLongPress = {},
          onTap = {}
        )
      }
  )

陈利津
  • 41
  • 3