Android Property Animations: Building Complex Animations

An animation using the MatrixEvaluator

The Animation Set allows you to choreograph a number of animations to play in parallel and in sequence. This gives you a great degree of artistic freedom. It also means that the standard API can get somewhat confusing if you want to programmatically create a complex animation set. To make things easier Android provides the AnimatorSet.Builder class that assists you in creating a complex Animation Set. As the name suggests, the Builder class implements the classic builder software design pattern but it does so in a slightly different way than you might be used to. A Builder cannot be created directly without an AnimatorSet object. Instead you need to create an empty AnimatorSet and then create a Builder by calling AnimatorSet.play. The Builder will then add animations to the set. It will also set the parameters so that animations are timed correctly and played in the right order. Using the Builder is comparatively easy and makes the code manageable even for complex animations.

Building an Animation using the Builder

As always, the best way to learn is by example. In this tutorial I will create an animation of an image moving along a set path on the screen. I start with a simple frame layout that contains the image.

<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=".MainActivity" >

  <ImageView
    android:id="@+id/some_image"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:layout_marginLeft="20dp"
    android:layout_marginTop="20dp"
    android:cropToPadding="true"
    android:src="@drawable/ferriswheel"
    android:text="@string/example1"
    android:onClick="startAnimation" />

</FrameLayout>

The image has a width and height of 100dp and is located in the top right corner of the screen. The onClick callback is set to startAnimation, a method that we will now define in the Activity. I want the animation to consist of three parts. First the ImageView should move right, from an x-coordinate of 20dp to 220dp. Then it should move down, changing the y-coordinate from 20dp to 220dp. In the final part the view should move back to its original position by changing both coordinates at the same time. This will result in a diagonal movement. Because ObjectAnimator can only animate one property at a time, we will need two animators for this last part. In total we will need to create four Object Animators. The startAnimation method will begin as follows.

public void startAnimation(View view) {
  float scale = getResources().getDisplayMetrics().density;
  View someImage = findViewById(R.id.some_image);

  ObjectAnimator anim1 = ObjectAnimator.ofFloat(someImage, 
    "x", 20.0f*scale, 220.0f*scale);
  ObjectAnimator anim2 = ObjectAnimator.ofFloat(someImage, 
    "y", 20.0f*scale, 220.0f*scale);
  ObjectAnimator anim3 = ObjectAnimator.ofFloat(someImage, 
    "x", 220.0f*scale, 20.0f*scale);
  ObjectAnimator anim4 = ObjectAnimator.ofFloat(someImage, 
    "y", 220.0f*scale, 20.0f*scale);

The first line defines a screen density scale that allows conversion from dips to pixels. The second line will retrieve the View that contains the image. The following lines create our four ObjectAnimator objects, each changing either the x or y property of the View containing the image. After this we need to assemble the object animators into an animation set. We first create the set itself.

  AnimatorSet animSet = new AnimatorSet();

This is done in just the same way as in the previous tutorial, just by calling the default constructor. Next we have to tell the set which animations to play and in which order. By calling animSet.play we create a builder object that can be used to add animations and also specify the delay and playing order. Even though we will be using the AnimatorSet.Builder class, one will usually not need to hold a reference to a Builder object. All calls on the Builder object can be conveniently coded in the same line that created the object. Let’s look at the example.

  animSet.play(anim1).before(anim2);
  animSet.play(anim3).after(anim2);
  animSet.play(anim3).with(anim4);
  animSet.play(anim1).after(500);

The call to animSet.play(anim1) creates a Builder object that uses anim1 as a reference animation. It will also add anim1 to the set. The call to before(anim2) on that Builder adds anim2 and will schedule anim1 to run before anim2. The Builder class defines another method called after that allows you to specify the order in reverse. The second line in the code block above will create a Builder with anim3 as reference animation by calling play(anim3). It will then schedule anim3 to be played after anim2. In order to make two animations run at the same time you can use the Builder method with. The third line above demonstrates this. Here anim3 and anim4 are played at the same time. Finally we can specify a delay of an animation. In order to start the first animation anim1 with a delay of 500 milliseconds the fourth line creates a Builder referencing anim1 and calls the method after that take a delay as argument.

One feature of the builder design pattern is the fact that the methods called on the builder return the Builder object itself. This means that calls to the Builder can be chained together. The four lines above can be abbreviated into the following two lines.

animSet.play(anim1).before(anim2).after(500);
animSet.play(anim3).after(anim2).with(anim4);

All Builder calls that have the same reference animation can be chained together. The first line in this abbreviated code tells the AnimatorSet that anim1 should be played before anim2 but after a delay of 500 milliseconds. The second line will play anim3 after anim2 but at the same time as anim4.

  animSet.setDuration(1000);
  animSet.setInterpolator(new AccelerateDecelerateInterpolator());
  animSet.start();

AnimatorSet exampleFinally, after specifying the playing order of the animation set, we have to start the animation. We set the duration to 1000 milliseconds or 1 second. There is a potential pitfall here. The duration specifies the duration of each animation contained in the set and not the total duration of the AnimatorSet. In our case, each of the four animations will play for 1 second and, because there are three animations in sequence, the total animation will last 3 seconds. The next line specifies the interpolator to use for the animations. Again, this specifies the interpolator for each member of the AnimatorSet. Each of the object animations, anim1 to anim4, will use the AccelerateDecelerateInterpolator for the motion of the view, meaning that the image will accelerate and the decelerate in the horizontal motion (anim1), before accelerating and decelerating in the vertical motion (anim2), and so on. The call to animSet.start will start playing the animation. The result can be seen on the right.

Fine Grained Control

You can specify individual durations and interpolators for each ObjectAnimator by calling setDuration or setInterpolator on the relevant animators. In this example I will modify the animation above. The horizontal and vertical motion will be faster and I will try to make the first two animation appear to be one smooth movement by using interpolators.

  anim1.setDuration(500);
  anim2.setDuration(500);
  anim1.setInterpolator(new AccelerateInterpolator());
  anim2.setInterpolator(new DecelerateInterpolator());

The duration of both anim1 and anim2 have been reduced to 500 milliseconds. By using AccelerateInterpolator for anim1 and DecelerateInterpolator for anim2, the image will accelerate towards the right and then speed through the top right corner before decelerating downward.

The motion back towards the top left corner should be slower and should follow an s-shaped path. This will be achieved by setting different interpolators for the two animations anim3 and anim4, that are playing in parallel.

  anim3.setDuration(1000);
  anim4.setDuration(1000);
  anim3.setInterpolator(new LinearInterpolator());
  anim4.setInterpolator(new AccelerateDecelerateInterpolator());

AnimatorSet Fine Grained ControlThe duration for the two animations is 1000 milliseconds. I choose the LinearInterpolator for the x-motion and for the y-motion I choose the AccelerateDecelerateInterpolator. I discussed in the tutorial on interpolators that the AccelerateDecelerateInterpolator defines a sinusoidal interpolation curve. The linear x-motion interpolation provides a regular motion on which the sinusoidal path becomes visible. The result can be seen on the right.

I hope you found this tutorial helpful. As always, the sources can be found on Github.

Follow the author

5 Comments

  1. Sunny

    I was going to write a small tut on this though not as detailed and informative like yours. Thank you for clarifying a lot of my doubts.

    Reply
  2. Dennis

    Thank you, it was useful for me. I would like to add that setDuration, setDelay and setInterpolator are also applicable to animation sets.

    Reply
  3. Vijay

    Do you have an example where two images are randomly animated (this I can do following your examples) and when they come close to each other we change scene where the images follow a different path, or even a third image joins the animation.

    Reply
    1. Mikail (Post author)

      Hi Vijay,

      Android Animations don’t provide support for collision detection. In order to achieve what you are looking for you could write your own ValueAnimator.AnimatorUpdateListener and manually check for collisions and control the animations on the screen.

      Reply
  4. Phebe

    Thanks for the nice tuts. But I was wondering, is there any way we can make the animation smoother for ObjectAnimators?

    Reply

Leave a Comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>