Flip animations can create a more enhanced feel for your app by making it more playful, especially when displaying notifications. Here’s how to implement a page flipping animation.

Create views

A card has two sides and each side needs to be a separate layout.  Create 2 layouts, a back layout and a front layout . The front view will contain an image and the back view will contain a description. Here is the layout for the front of the card which shows an image. Put it into a file called front.xml under “res/layout”:

<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/dessert"
    android:scaleType="centerCrop"
    android:contentDescription="YUMMY" />

Next is the layout for the back side, which shows text that gives a description of the image. Put the following XML into back.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#a6c"
    android:padding="16dp"
    android:gravity="bottom">
    <TextView android:id="@android:id/text1"
        style="?android:textAppearanceLarge"
        android:textStyle="bold"
        android:textColor="#fff"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="YUMMY" />   
</LinearLayout>

Create the Animators

Animators are used to control the visual elements. You will need four animators for when the card animates out to the left, out to the right, in to the right and in to the left. The effect of the first animator is to rotate the back of the card into the view. Here is the XML for res/animator/left_in.xml:

<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" />
    <!-- Rotate. -->
    <objectAnimator
        android:valueFrom="-180"
        android:valueTo="0"
        android:propertyName="rotationY"
        android:interpolator="@android:interpolator/accelerate_decelerate"
        android:duration="3000" />
    <objectAnimator
        android:valueFrom="0.0"
        android:valueTo="1.0"
        android:propertyName="alpha"
        android:startOffset="1500"
        android:duration="1" />
</set>

The effect of this next animator is to rotate the card’s front out of view. Put the following XML in animator/left_out.xml:

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Rotate. -->
    <objectAnimator
        android:valueFrom="0"
        android:valueTo="180"
        android:propertyName="rotationY"
        android:interpolator="@android:interpolator/accelerate_decelerate"
        android:duration="3000" />
    <!-- Half-way through the rotation (see startOffset), set the alpha to 0. -->
    <objectAnimator
        android:valueFrom="1.0"
        android:valueTo="0.0"
        android:propertyName="alpha"
        android:startOffset="1500"
        android:duration="1" />
</set>

The third animator rotates the front of the card in to view. Put this code in animator/right_in.xml:

<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" />
    <!-- Rotate. -->
    <objectAnimator
        android:valueFrom="180"
        android:valueTo="0"
        android:propertyName="rotationY"
        android:interpolator="@android:interpolator/accelerate_decelerate"
        android:duration="3000" />
    <objectAnimator
        android:valueFrom="0.0"
        android:valueTo="1.0"
        android:propertyName="alpha"
        android:startOffset="1500"
        android:duration="1" />
</set>

The final animator is used to rotate the back of the card in to the view. Here is the XML for animator/right_out.xml:

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Rotate. -->
    <objectAnimator
        android:valueFrom="0"
        android:valueTo="-180"
        android:propertyName="rotationY"
        android:interpolator="@android:interpolator/accelerate_decelerate"
        android:duration="3000" />
    <!-- Half-way through the rotation (see startOffset), set the alpha to 0. -->
    <objectAnimator
        android:valueFrom="1.0"
        android:valueTo="0.0"
        android:propertyName="alpha"
        android:startOffset="1500"
        android:duration="1" />
</set>

Create Fragments and animate the Flips

Create the back and front fragment classes, and a layout to display the fragments. You also need to set the fragment that will be displayed by default when the activity launches. Here is the layout for displaying the fragments, which you can use to add fragments at runtime. Put this code in layout/activity_flip.xml:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Here are the fragment classes for the back and front of the card:

public class FlipActivity extends Activity {
    ...
    public static class CardFrontFragment extends Fragment {
        public CardFrontFragment() {}
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
            return inflater.inflate(R.layout.fragment_card_front, container, false);
        }
    }
    public static class CardBackFragment extends Fragment {
        public CardBackFragment() {}
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
            return inflater.inflate(R.layout.fragment_card_back, container, false);
        }
    }
}

In the FlipActivity code, set the content view to be the FrameLayout you just created. Decide which card you want to display by default. In this example, we are going to display the front of the card. Here is how to display a default fragment when the activity is created.

public class FlipActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_flip);
        if (savedInstanceState == null) {
            getFragmentManager()
                .beginTransaction()
                .add(R.id.container, new CardFrontFragment())
                .commit();
        }
        ...
    }

When you open the app for the first time, the front of the card containing an image will be displayed. Let’s configure an action that will display the back of the card. The code below will show the other side of the card and incorporate the following actions:

  • It sets the animations that you created for the fragment transitions.
  • It replaces the currently displayed fragment with a new fragment and animates this event with your animations.
  • It adds the previously displayed fragment to the fragment back pile consequently when the user presses the Back button, the card flips backward.
private void flipCard() {
    if (mShowingBack) {
        getFragmentManager().popBackStack();
        return;
    }
    // Flip to the back.

    mShowingBack = true;
    getFragmentManager()
        .beginTransaction()
        .setCustomAnimations(
            R.animator.right_in, R.animator.right_out,
            R.animator.left_in, R.animator.left_out)
        .replace(R.id.container, new CardBackFragment())
        // Add this transaction to the back stack, allowing users to press Back
        // to get to the front of the card.
        .addToBackStack(null)
        // Commit the transaction.
        .commit();
}

Now create the menu items which will show flip animations when clicked. In the menu/main.xml, add the following menu items:

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/action_add_item"
        android:title="@string/action_add_item"
        android:icon="@drawable/ic_action_new"
        android:showAsAction="ifRoom" />
</menu>

Next, define menu item Id’s for instantiating bar items, create a values resource (values/action.xml) and add the following XML code to it:

<resources>
    <item type="id" name="action_next" />
    <item type="id" name="action_flip" />
</resources>

Update the Activity class by inflating the menu with the items you created above and instantiate the flipCard() method when a menu item is clicked.

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    MenuItem item = menu.add(Menu.NONE, R.id.action_flip, Menu.NONE,
        mShowingBack ? R.string.action_photo :
        R.string.action_info);
    item.setIcon(mShowingBack ?
        R.drawable.ic_action_photo :
        R.drawable.ic_action_info);
    item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
    return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case android.R.id.home:
            NavUtils.navigateUpTo(this, new Intent(this, FlipActivity.class));
            return true;
        case R.id.action_flip:
            flipCard();
            return true;
    }
    return super.onOptionsItemSelected(item);
}

The final code for the activity (FlipActivity.java) should look like this:

package com.example.vaatiesther.flipactionanimation;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
public class FlipActivity extends Activity
implements FragmentManager.OnBackStackChangedListener {
    private boolean mShowingBack = false;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_flip);
        if (savedInstanceState == null) {
            getFragmentManager()
                .beginTransaction()
                .add(R.id.container, new CardFrontFragment())
                .commit();
        } else {
            mShowingBack = (getFragmentManager().getBackStackEntryCount() > 0);
        }
        getFragmentManager().addOnBackStackChangedListener(this);
    }
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        MenuItem item = menu.add(Menu.NONE, R.id.action_flip, Menu.NONE,
            mShowingBack ?
            R.string.action_photo :
            R.string.action_info);
        item.setIcon(mShowingBack ?
            R.drawable.ic_action_photo :
            R.drawable.ic_action_info);
        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
        return true;
    }
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                // Navigate "up" the demo structure to the launchpad activity.
                // See http://developer.android.com/design/patterns/navigation.html for more.
                NavUtils.navigateUpTo(this, new Intent(this, FlipActivity.class));
                return true;
            case R.id.action_flip:
                flipCard();
                return true;
        }
        return super.onOptionsItemSelected(item);
    }
    private void flipCard() {
        if (mShowingBack) {
            getFragmentManager().popBackStack();
            return;
        }
        // Flip to the back.
        mShowingBack = true;
        getFragmentManager()
            .beginTransaction()
            .setCustomAnimations(
                R.animator.right_in, R.animator.right_out,
                R.animator.left_in, R.animator.left_out)
            .replace(R.id.container, new CardBackFragment())
            // Add this transaction to the back stack, allowing users to press Back
            // to get to the front of the card.
            .addToBackStack(null)
            // Commit the transaction.
            .commit();
    }
    @Override
    public void onBackStackChanged() {
        mShowingBack = (getFragmentManager().getBackStackEntryCount() > 0);
        // When the back stack changes, invalidate the options menu (action bar).
        invalidateOptionsMenu();
    }
    public static class CardFrontFragment extends Fragment {
        public CardFrontFragment() {}
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
            return inflater.inflate(R.layout.fragment_card_front, container, false);
        }
    }
    public static class CardBackFragment extends Fragment {
        public CardBackFragment() {}
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
            return inflater.inflate(R.layout.fragment_card_back, container, false);
        }
    }
}

The final result should look like this:

Conclusion

Flip animations can help to enhance your app and move its UI from the mundane to a great visual experience. Do you use flip animations your app? Let me know in the comments below!