Delightful details: Lift-on-touch animation for CardView

Ever since Google Now introduced the playful cards UI on Android, cards have become a core UI component of the platform. With Material Design, cards were officially incorporated into Android's UI toolkit, accompanied by Matias Duarte's lovingly-crafted guidelines on exactly how they should look and feel.

The CardView implementation takes care of ensuring that cards adhere to the spec, but it doesn't implement one crucial interaction out-of-the-box: the lift-on-touch effect specified in the Responsive Interaction section:

Strangely none of Google's own apps care: I checked the latest versions of Now, Play Music, Play Store, Newsstand. Why throw away a simple opportunity for responsive interaction? Here's how to achieve that effect on API 21+ (Lollipop and above only). It really is quite simple.

Code

First, add this StateListAnimator to your res/anim-v21 directory (let's call it lift_on_touch.xml):

<?xml version="1.0" encoding="utf-8"?>  
<!-- animate the translationZ property of a view when pressed -->  
<selector xmlns:android="http://schemas.android.com/apk/res/android">  
    <item
        android:state_enabled="true"
        android:state_pressed="true">
        <set>
            <objectAnimator
                android:duration="@android:integer/config_shortAnimTime"
                android:propertyName="translationZ"
                android:valueTo="6dp"
                android:valueType="floatType"/>
        </set>
    </item>
    <item>
        <set>
            <objectAnimator
                android:duration="@android:integer/config_shortAnimTime"
                android:propertyName="translationZ"
                android:valueTo="0"
                android:valueType="floatType"/>
        </set>
    </item>
</selector>  

This animator moves the view it's applied to 6dp up along the Z-axis, which is visually seen as the shadow underneath it expanding. Why 6dp? Because it's the value used by the Google I/O 2014 app and also mentioned in the documentation at one point.

Next, in your layout XML, you just have to specify this StateListAnimator1:

<android.support.v7.widget.CardView  
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:foreground="?attr/selectableItemBackground"
    android:stateListAnimator="@anim/lift_on_touch"
    android:clickable="true"
    app:cardPreventCornerOverlap="false"
    app:cardUseCompatPadding="true">

    <!-- card content goes here -->

</android.support.v7.widget.CardView>  

(Thanks to Ben Carver and Fatih Coşkun in the comments thread, for pointing out the occasional need for android:clickable; it is not needed if you call setOnClickListener on the CardView as shown below, which is going to be the case most of the time).

Or you can do it in code if you prefer:

CardView card = findViewById(R.id.card);  
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {  
    StateListAnimator stateListAnimator = AnimatorInflater
            .loadStateListAnimator(context, R.anim.lift_on_touch);
    card.setStateListAnimator(stateListAnimator);
}
// add a click handler to ensure the CardView handles touch events
// otherwise the animation won't work
card.setOnClickListener(/* ... */);  

Note that you can use the same StateListAnimator to add the lift-on-touch effect to any View on API 21+. In fact I just sent in a Github pull request to add this missing piece of interaction to Melnykov Oleksandr's awesome FloatingActionButton library. Nice eh?

Feel free to post a comment below if you have anything to add!

  1. You might be wondering, if StateListAnimator is only available on API 21+, how are we referring to it directly in our layout? Here's the trick: "When parsing XML resources, Android ignores XML attributes that aren’t supported by the current device. So you can safely use XML attributes that are only supported by newer versions without worrying about older versions breaking when they encounter that code."

comments powered by Disqus