Ripple effect
Android Lollipop
CardView
Android development
UI design

Ripple effect on Android Lollipop CardView

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

On Android 5.0 and later, the ripple effect is the standard touch feedback for Material-style components. With CardView, the main task is not enabling the ripple itself, but attaching it to the clickable surface in a way that still looks correct with elevation and rounded corners.

The Simplest XML Setup

If your CardView is the interactive element, make it clickable and assign a selectable foreground. Since CardView derives from FrameLayout, it can display a foreground drawable on Lollipop and later.

xml
1<androidx.cardview.widget.CardView
2    xmlns:android="http://schemas.android.com/apk/res/android"
3    xmlns:app="http://schemas.android.com/apk/res-auto"
4    android:id="@+id/profile_card"
5    android:layout_width="match_parent"
6    android:layout_height="wrap_content"
7    android:clickable="true"
8    android:focusable="true"
9    android:foreground="?attr/selectableItemBackground"
10    app:cardCornerRadius="12dp"
11    app:cardElevation="6dp">
12
13    <LinearLayout
14        android:layout_width="match_parent"
15        android:layout_height="wrap_content"
16        android:orientation="vertical"
17        android:padding="16dp">
18
19        <TextView
20            android:layout_width="wrap_content"
21            android:layout_height="wrap_content"
22            android:text="Open profile" />
23
24    </LinearLayout>
25
26</androidx.cardview.widget.CardView>

That gives you default Material ripple feedback without writing custom drawables.

Handling Clicks in Code

The ripple only shows when the view is actually interactive, so wire the click listener to the same view that owns the foreground.

kotlin
1val card = findViewById<CardView>(R.id.profile_card)
2card.setOnClickListener {
3    Toast.makeText(this, "Card tapped", Toast.LENGTH_SHORT).show()
4}

If you put the click listener on a child view instead, the user may tap the card and see no ripple where they expect it.

Using a Custom Ripple Color

The default theme ripple is often enough, but you can define your own ripple drawable when design requires a custom color.

xml
1<!-- res/drawable/card_ripple.xml -->
2<ripple xmlns:android="http://schemas.android.com/apk/res/android"
3    android:color="@color/card_ripple_color">
4    <item android:id="@android:id/mask">
5        <shape android:shape="rectangle">
6            <solid android:color="@android:color/white" />
7            <corners android:radius="12dp" />
8        </shape>
9    </item>
10</ripple>

Then apply it as the foreground:

xml
1<androidx.cardview.widget.CardView
2    android:layout_width="match_parent"
3    android:layout_height="wrap_content"
4    android:foreground="@drawable/card_ripple"
5    android:clickable="true"
6    android:focusable="true"
7    app:cardCornerRadius="12dp">
8</androidx.cardview.widget.CardView>

The mask is important when you want the ripple to visually respect rounded edges rather than appearing as a plain rectangle.

Why Some Card Ripples Look Wrong

CardView combines shadows, rounded corners, and content padding. That means a ripple can appear slightly misaligned or extend past the visually expected edge if the foreground is not masked carefully.

When that happens, two patterns help:

  • Put the ripple on an inner container that matches the visible content area.
  • Use a custom ripple mask with the same radius as the card.

For older support-library layouts, developers often wrapped content in a child FrameLayout and placed the foreground there because it gave more predictable clipping behavior.

A Practical Inner-Container Pattern

If the direct CardView foreground does not behave the way you want, move the ripple to the child content container:

xml
1<androidx.cardview.widget.CardView
2    android:layout_width="match_parent"
3    android:layout_height="wrap_content"
4    app:cardCornerRadius="12dp"
5    app:cardElevation="6dp">
6
7    <FrameLayout
8        android:id="@+id/card_content"
9        android:layout_width="match_parent"
10        android:layout_height="wrap_content"
11        android:background="@android:color/white"
12        android:clickable="true"
13        android:focusable="true"
14        android:foreground="@drawable/card_ripple"
15        android:padding="16dp" />
16
17</androidx.cardview.widget.CardView>

This pattern is often easier to tune because the inner layout owns both the visible background and the interaction state.

Common Pitfalls

  • Adding a ripple drawable without making the touched view clickable and focusable.
  • Wiring the click listener to a child view while expecting the outer card to show the feedback correctly.
  • Assuming the default ripple will always respect rounded corners without a custom mask.
  • Ignoring padding and nested containers when the ripple looks offset or clipped oddly.
  • Sticking with old CardView patterns when MaterialCardView would fit the current design system better.

Summary

  • The easiest ripple setup is a clickable CardView with a selectable foreground.
  • Attach the click listener to the same view that owns the ripple.
  • Use a custom ripple drawable and mask when rounded corners need precise clipping.
  • An inner clickable container can behave more predictably than the outer card.
  • For newer apps, MaterialCardView is often a better long-term choice than plain CardView.

Course illustration
Course illustration

All Rights Reserved.