Read how to make your Android App more delightful using Card Flip Animation.

For a long time, I’ve tried to implement Card Flip Animation, but every tutorial or source code I’ve found didn’t explain “magic” numbers in XML animation files. Out of nowhere wild hackathon appeared!

On our company’s latest hackathon we decided to develop an app for Planning Poker. As you can guess Flip Animation was very important to make our app delightful. I was chosen to create UI and it was a nice opportunity to dive into Flip Animation. In this blog post, I will explain everything I have learned about it.

Card layouts

At first, let’s create a simple layout with a front of a card:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:tint="@color/cardFront" android:padding="16dp" android:src="@drawable/rectangle"/> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:text="@string/front" android:textColor="@color/white" style="@style/Base.TextAppearance.AppCompat.Display1" android:gravity="center"/> </FrameLayout> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 < FrameLayout xmlns : android = "http://schemas.android.com/apk/res/android" android : layout_width = "match_parent" android : layout_height = "match_parent" android : orientation = "vertical" > < ImageView android : layout_width = "match_parent" android : layout_height = "match_parent" android : tint = "@color/cardFront" android : padding = "16dp" android : src = "@drawable/rectangle" / > < TextView android : layout_width = "match_parent" android : layout_height = "match_parent" android : text = "@string/front" android : textColor = "@color/white" style = "@style/Base.TextAppearance.AppCompat.Display1" android : gravity = "center" / > < / FrameLayout >

and put it to activity layout.

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="blog.droidsonroids.pl.blogpost.MainActivity" android:onClick="flipCard"> <FrameLayout android:id="@+id/card_front" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center"> <include layout="@layout/card_front" /> </FrameLayout> </FrameLayout> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 < FrameLayout xmlns : android = "http://schemas.android.com/apk/res/android" xmlns : tools = "http://schemas.android.com/tools" android : layout_width = "match_parent" android : layout_height = "match_parent" tools : context = "blog.droidsonroids.pl.blogpost.MainActivity" android : onClick = "flipCard" > < FrameLayout android : id = "@+id/card_front" android : layout_width = "match_parent" android : layout_height = "match_parent" android : gravity = "center" > < include layout = "@layout/card_front" / > < / FrameLayout > < / FrameLayout >

Animation XML

After that, create the first animation XML.

<set xmlns:android="http://schemas.android.com/apk/res/android"> <objectAnimator android:valueFrom="0" android:valueTo="180" android:propertyName="rotationY" android:duration="@integer/anim_length" /> </set> 1 2 3 4 5 6 7 8 < set xmlns : android = "http://schemas.android.com/apk/res/android" > < objectAnimator android : valueFrom = "0" android : valueTo = "180" android : propertyName = "rotationY" android : duration = "@integer/anim_length" / > < / set >

This simple animation just rotates the view around Y axis from 0 to 180 degree and it is what we need for first part of our animation. As it is shown below, animation disappears half-way through, but I will explain later how to fix it.

The second part of the animation is just “opposite” to what we’ve just created. So, I changed two values in our XML file

<set xmlns:android="http://schemas.android.com/apk/res/android"> <objectAnimator android:valueFrom="-180" android:valueTo="0" android:propertyName="rotationY" android:duration="@integer/anim_length" /> </set> 1 2 3 4 5 6 7 8 < set xmlns : android = "http://schemas.android.com/apk/res/android" > < objectAnimator android : valueFrom = "-180" android : valueTo = "0" android : propertyName = "rotationY" android : duration = "@integer/anim_length" / > < / set >

and see what happened:

-180 value causes the view to flip immediately and start rotating it to its natural position. The sign decides about the direction of motion. Feel free to experiment and change values for better understanding a problem and don’t worry about that ugly glitch at animation start because that will be unnoticeable for a user.

To make the whole animation, we need to create two XML files for enter and exit animation and fix disappearing card on half-way through animation. To fix it we just need to change camera distance. Camera distance is the length between animated view and a “virtual” eye. Changing that distance affects the perspective distortion.

LET’S TALK ABOUT YOUR APP We’re 100% office based team with 7-years’ experience

in mobile & web app development Estimate project

Camera distance

In our case distance is just to small and Flipping Animation crosses the “virtual line” and disappears. To fix it we just need to increase that distance. The default distance is different on various devices. According to Android documentation, if you want to specify a distance that leads to visually consistent result across various devices use that formula:

float scale = context.getResources().getDisplayMetrics().density; view.setCameraDistance(distance * scale); 1 2 float scale = context . getResources ( ) . getDisplayMetrics ( ) . density ; view . setCameraDistance ( distance * scale ) ;

I set the distance to 8000 to make animation perspective less distorted.

Whole animation

A second thing we need to do is hide/show view in half of the animation, to achieve this, simply add following lines

<objectAnimator android:valueFrom="1.0" android:valueTo="0.0" android:propertyName="alpha" android:duration="0" /> 1 2 3 4 5 < objectAnimator android : valueFrom = "1.0" android : valueTo = "0.0" android : propertyName = "alpha" android : duration = "0" / >

to every animation file. Let’s take a look at out_animation.xml (animation of foreground view).

<set xmlns:android="http://schemas.android.com/apk/res/android"> <objectAnimator android:valueFrom="0" android:valueTo="180" android:propertyName="rotationY" android:duration="@integer/anim_length" /> <objectAnimator android:valueFrom="1.0" android:valueTo="0.0" android:propertyName="alpha" android:startOffset="@integer/anim_length_half" android:duration="1" /> </set> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 < set xmlns : android = "http://schemas.android.com/apk/res/android" > < objectAnimator android : valueFrom = "0" android : valueTo = "180" android : propertyName = "rotationY" android : duration = "@integer/anim_length" / > < objectAnimator android : valueFrom = "1.0" android : valueTo = "0.0" android : propertyName = "alpha" android : startOffset = "@integer/anim_length_half" android : duration = "1" / > < / set >

This animation rotates view from a natural position to mirrored position around the Y-axis and hides it half-way.

A half-way animation is achieved by setting startOffset for half of the time of the whole animation. Next in_animation.xml (animation of background view) looks almost the same:

<set xmlns:android="http://schemas.android.com/apk/res/android"> <objectAnimator android:valueFrom="1.0" android:valueTo="0.0" android:propertyName="alpha" android:duration="0" /> <objectAnimator android:valueFrom="-180" android:valueTo="0" android:propertyName="rotationY" android:repeatMode="reverse" android:duration="@integer/anim_length" /> <objectAnimator android:valueFrom="0.0" android:valueTo="1.0" android:propertyName="alpha" android:startOffset="@integer/anim_length_half" android:duration="0" /> </set> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 < set xmlns : android = "http://schemas.android.com/apk/res/android" > < objectAnimator android : valueFrom = "1.0" android : valueTo = "0.0" android : propertyName = "alpha" android : duration = "0" / > < objectAnimator android : valueFrom = "-180" android : valueTo = "0" android : propertyName = "rotationY" android : repeatMode = "reverse" android : duration = "@integer/anim_length" / > < objectAnimator android : valueFrom = "0.0" android : valueTo = "1.0" android : propertyName = "alpha" android : startOffset = "@integer/anim_length_half" android : duration = "0" / > < / set >

At first, a view is set to invisible, then animation starts and half-way through it, the view is set to visible.

Result

This is what we achieve by combining two animations:

Wrap up

I hope that after reading this post you will be no more afraid of implementing a Flip Animation and your apps will look more delightful than ever.