Lesson Weekend

Now that our list of saved restaurants is responding correctly to drag and drop and swipe events, let's add some additional finishing touches. In this lesson we will explore adding subtle animations that appear when a user interacts with a component using gestures. This will make our application feel more interactive, providing a more engaging experience for users. As Google's Material Design Specifications state, "Motion design can effectively guide the user's attention in ways that both inform and delight...".

There are two primary ways to create animations: With Java code, and with XML. This lesson will walk through implementing the same animations in each of these two ways. Afterwards, we'll discuss the pros and cons of each approach.

Additionally, note that this lesson contains two example MyRestaurants repositories. One with programmatic animations, and another with XML animations.

Animation Setup

Both methods to create animations require the same general setup:

  • An ItemTouchHelperViewHolder interface to define methods that will be called when an item is selected for a drag-and-drop gesture.
  • This interface needs to be implemented in our FirebaseRestaurantViewHolder, where we will override its methods.
  • We'll need to create a SimpleItemTouchHelperCallback class. This will listen for users selecting items, and inform the view holder when a gesture requiring animation is being performed.

ItemTouchHelperViewHolder

First, let's create the required ItemTouchHelperViewHolder interface in our util sub-package and define its methods:

ItemTouchHelperViewHolder.java
public interface ItemTouchHelperViewHolder {
    void onItemSelected();
    void onItemClear();
}
  • onItemSelected() will handle updating the appearance of a selected item while the user is dragging-and-dropping it.

  • onItemClear() will remove the 'selected' state (and therefore the corresponding changes in appearance) from an item.

We'll implement this interface in the FirebaseRestaurantViewHolder, and override its two methods:

FirebaseRestaurantViewHolder.java
public class FirebaseRestaurantViewHolder extends RecyclerView.ViewHolder implements ItemTouchHelperViewHolder {
  ...

    @Override
    public void onItemSelected() {
        Log.d("Animation", "onItemSelected");
        // we will add animations here
    }

    @Override
    public void onItemClear() {
        Log.d("Animation", "onItemClear");
        // we will add animations here
    }
}

For now, each method will only contain log statements. After we complete the next step and update the SimpleItemTouchHelperCallback we'll use these logcat statements to confirm our methods are being triggered correctly. When they are, we'll replace them with code to create animations in our user interface.

Updating SimpleItemTouchHelperCallback to Handle Animating

Next, we must update our SimpleItemTouchHelperCallback to handle the onItemSelected() and onItemCleared() triggers. We'll add the following methods. The embedded comments below contain an explanation of each:

SimpleItemTouchHelperCallback.java
public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {

    ...

      //   The method below triggers the callback in ItemTouchHelperViewHolder which is then sent to our
      //  RestaurantListViewHolder where we will later add animations.

    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {

          //  This conditional ensures we only change appearance of active items:

        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
            if (viewHolder instanceof ItemTouchHelperViewHolder) {

                //  This tells the viewHolder that an item is being moved or dragged:

                ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
                itemViewHolder.onItemSelected();
            }
        }
        super.onSelectedChanged(viewHolder, actionState);
    }

      //  This triggers the callback in the ItemTouchHelperViewHolder which will be sent to our RestaurantListViewHolder.
      //  Then, in the clearView override in RestaurantListViewHolder, we will remove the animations attached
      //   to 'selected' items, since this item will no longer be actively selected.

    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
        if (viewHolder instanceof ItemTouchHelperViewHolder) {

            //  Tells the view holder to return the item back to its normal appearance:

            ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
            itemViewHolder.onItemClear();
        }
    }
}

Now we should now be able to run our app, and drag-and-drop list items from the "Saved Restaurants" area to ensure our log statements are being triggered correctly. If so, we're ready to add either Programmatic or XML animations.

Programmatic Animations

First, we'll walk through the process of adding animations programmatically.

Required Reading

Android provides a variety of APIs for applying animation to the user interface. The Properties Animation API is one such tool that allows us to easily alter properties of view elements over a given duration.

Before we begin, read more about property animation on the Android Developers Site and in this blog post regarding ViewPropertyAnimator.

SavedRestaurantListActivity Animations

Let’s utilize property animations on the restaurant list items users are able to drag and drop in their "Saved Restaurants" list. We'll add the following code to the RestaurantViewHolder:

FirebaseRestaurantViewHolder.java
public class FirebaseRestaurantViewHolder extends RecyclerView.ViewHolder implements ItemTouchHelperViewHolder {
  ...

    @Override
    public void onItemSelected() {
        itemView.animate()
                .alpha(0.7f)
                .scaleX(0.9f)
                .scaleY(0.9f)
                .setDuration(500);
    }

    @Override
    public void onItemClear() {
        itemView.animate()
                .alpha(1f)
                .scaleX(1f)
                .scaleY(1f);
    }

...

Here, we call .animate() on ItemView, and chain the following methods to create our custom animation:

  • .scaleX() sets the horizontal scale of the item. It takes a float value as an argument.
  • .scaleY() sets the vertical scale of the item. It also takes a float value as an argument
  • .alpha() alters the alpha level of an object (its transparency and/or opaqueness, essentially).
  • .setDuration() determines how long this animation will last. It takes a millisecond value as an argument.

Now, if we launch the application in our emulator we should see that the process of dragging-and-dropping items in our "Saved Restaurants" list is now animated!

Example GitHub Repo for MyRestaurants with Programmatic Animations


XML Property Animations

Next, let's walk through the process of creating the same animations using XML.

Required Reading

Before we begin exploring this, read the Android Guide on Declaring Animations in XML.

Animator Resource Directory

XML animations need to reside in a special directory called animator. Right-click on your existing res directory, and select New > Android resource directory . In the resulting window name the new directory animator and set the 'resource type' option to animator:

creating-animator-directory

XML Animation Resources

Within this directory we'll create two new XML files: drag_scale_on.xml and drag_scale_off.xml. The first will define the visual appearance of an element when it's currently being dragged by a user, and the latter will define its appearance when it's no longer being dragged. As users perform gestures to interact with the individual restaurant list items, the drag_scale_on, or drag_scale_off appearances will be toggled.

First, let's create drag_scale_on.xml. Create this file by right-clicking on our new animator resource directory, and selecting New > Animator resource file. We'll populate this layout with the following code:

animator/drag_scale_on.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:propertyName="scaleX"
        android:valueTo="0.9f"
        android:duration="500" />
    <objectAnimator
        android:propertyName="scaleY"
        android:valueTo="0.9f"
        android:duration="500" />
    <objectAnimator
        android:propertyName="alpha"
        android:valueTo="0.7f"
        android:duration="500" />
</set>

Here, you may notice some familiar terms: scaleX, scaleY, alpha and duration all refer to the same values as they did in the programmatic approach; here we're simply setting these values with XML instead. Additionally, the <set> tags refer to the AnimatorSet class, which is responsible for playing animations.

Next, let's make sure we include XML for when the animation is off. Create another XML file called drag_scale_off.xml and populate it with the following:

animator/drag_scale_off.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:propertyName="scaleX"
        android:valueTo="1f"
        android:duration="500" />
    <objectAnimator
        android:propertyName="scaleY"
        android:valueTo="1f"
        android:duration="500" />
    <objectAnimator
        android:propertyName="alpha"
        android:valueTo="1f"
        android:duration="500" />
</set>

As you can see, it contains all the same elements and XML selectors as drag_scale_on.xml, but with different values. This defines the appearance of the item when it is no longer being selected.

Inflating XML Animations

Now that we've created our XML animations, we'll need to inflate them in our ViewHolder. This is similar to the manner we inflate other types of XML layouts, but we'll use a special AnimatorSet object responsible for handling the corresonding <set> tags in our XML animations.

Notice that these are the same methods from the ItemTouchHelperViewHolder interface defined in the Setup section at the beginning of this lesson, and the same methods we used in the Programmatic Animations section. Make sure any code leftover from the programmatic approach has been commented out or removed from these methods before continuing.

FirebaseRestaurantViewHolder
...
 @Override
    public void onItemSelected() {
        AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(mContext,
                R.animator.drag_scale_on);
        set.setTarget(itemView);
        set.start();
    }

    @Override
    public void onItemClear() {
        AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(mContext,
                R.animator.drag_scale_off);
        set.setTarget(itemView);
        set.start();
    }
...

Here, we create a new AnimatorSet object to inflate and load the animations we defined in our corresponding <set> tags in our XML. Then, we call setTarget(), passing in itemView as an argument to instruct which element these animations should be applied to. Then, we call .start() to begin the specified animations.

Now, if we launch our application again, we should see the same animations when we drag-and-drop individual restaurant list items in the "Saved Restaurants" area of our application.

Example GitHub Repo for MyRestaurants with XML Animations

Programmatic vs. XML

As you can see, these are two slightly different approaches that accomplish the same thing. How do you know which to use in a given project? Let's briefly review the pros and cons of each approach:

XML

Pros

  • More easily re-usable, since you can simply inflate the XML file in multiple places throughout the application.
  • Easier to read; especially when multiple sets of animations are involved.
  • Provides clearer separation between an object and its behavior/appearance.

Cons

  • More verbose; ends up being more lines than adding animations programmatically.

Programmatic

Pros

  • Less code overall than creating animations in XML.
  • Fewer overall files in a project.

Cons

  • Can be more difficult to decipher, especially with multiple concurrent animations, or many properties.
  • Cannot be re-used as easily throughout an application.

Again, either of these two approaches are acceptable. When adding animations to an application, consider the pros and cons listed above, and decide which method works best for your specific animation.

Moving forward, be aware that the MyRestaurants example repository will include the XML animations. You are welcome to use either approach in your own MyRestaurants app.


Example GitHub Repo for MyRestaurants with Programmatic Animations

Example GitHub Repo for MyRestaurants with XML Animations