2D Animation for Android* Series: Comparing and Contrasting Different Ways of Doing the Same Animation

Part I: Introduction and Property Animation

In this blog series, I will compare and contrast the different ways of doing the same translation animation on a view in Android*. This will give you a better idea of how the various methods work and their differences, allowing you to compare the strengths and weaknesses of each method and determine which one is best for your purposes. There are many ways of doing animation in Android and this series will only be able to explore of subset of them, but should be sufficient to get you started. The code is designed to be adaptable and easily fits into other Android applications. I also use a simple true/false lock to illustrate the different animation listeners as well.

When you create an app, you want to make it visually exciting, so why not add some animation! In this series I animate the little chef icon in a restaurant app to call attention to the daily special. In part one we will address some of the concerns of screen location that come with translating an object, the setup of our restaurant app, and start off the animations with property animations. You can look forward to view animations in part two, then something a little different in part three with drawables and canvas, followed by part four which will cover the more complex SurfaceView and TextureView.

Series Outline:

Part I: Introduction and Property Animation

  1. Location Location Location
  2. Setup
  3. Property Animation
    1. Value Animation
    2. Object Animation
    3. Object Animation with Keyframes
    4. Object Animation with XML
    5. View Property Animation

Part II: View Animation

  1. View Animation (Non-Set and Set)
  2. View Animation with XML
  3. Custom Animation

Part 3: Drawable and Canvas

  1. Drawable
  2. Canvas

Part 4: SurfaceView and TextureView

  1. SurfaceView
  2. TextureView
  3. Combining Techniques

1. Location Location Location

You don’t want your animation wandering off the screen or running into other things unexpectedly. This becomes increasingly complex as you consider the array of different sized devices on the market and the dynamic nature of Android fragments and layouts you can have to accommodate the array of sizes. In the first few ways of animating, I choose to get the view’s position on the screen relative to its parent and then move it half that distance to the left and then back the same distance to the right to end where it started. Other animations discussed in later parts of this series will not have that same capability in our case and hence need to be handled a bit differently; this will be discussed further in-depth in each of their sections.

2. Setup

We are doing animation for our little chef image in a Restaurant App. You can see the code for the Restaurant app UI here: http://software.intel.com/en-us/android/articles/building-dynamic-ui-for-android-devices.  You can do this in whatever class that controls the screen where you want the animation. This is done in the MenuGridFragment.java in our case. Start out by defining the variables needed.

ImageView mLittleChef;
boolean simpleLock;
int mShortAnimationDuration;

Add the ImageView to the layout file fragment_menu_grid.xml.

<ImageView
            android:id="@+id/imageView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/dish_special" />

In the onCreateView, initialize our variables’ values. Note our menu page is a fragment inside of a view pager, you might need to do this in the onCreate or elsewhere depending on your code.

mLittleChef= (ImageView)rootView.findViewById(R.id.imageView1);
simpleLock=false;
mShortAnimationDuration=4000;

Inside our menu page, each animation is started by calling its respective method. This is triggered by clicking on an item in the menu only 4% of the time so as not to overwhelm the user with animations. You could use a timer instead, but using the click function will improve the chance that the user is actively using the application when the animation triggers. We also check the simpleLock variable as we want the animation to be able to fully complete before having to start again. This will also address the situation where the animation will start from the current position instead of the original position when it restarts for some methods.

/* Handle the event of item click from the menu grid */
public void onGridItemClick(MenuItem itemSelected, int position) {
		
	/* other grid clicking code */

	//Add the animation
	mDetailViewFragment.update(itemSelected);
	int animChance= (int) (Math.random() * 5);
	int animChance2= (int) (Math.random() * 5);
	if(!mGridViewFragment.simpleLock & (animChance== animChance2)){
		//pick animation method here
		mGridViewFragment.doValueAnimator(); 
		mGridViewFragment.simpleLock=true;
	}
} 

3. Property Animation

You can animate whatever object or value you want using property animation. In this case, we will be animating a view in order to better compare it with the ways of doing view animation. When using a property animation, the actual view/object properties will change, hence it will actually move in our case and not just update where it is drawn.

Note also that we are animating by updating the view’s x value. You could choose to update the view’s left/right value instead, but this will just be stretching the view in the case of property animations.

a. Value Animation

In value animation you define your ValueAnimator which creates a set of values between the given values. This animation set is completely separate from the actual view you want to animate. Think of the ValueAnimator more like a tool to give you a list of values to apply to your actual object. To get the current animated value you have to add an AnimatorUpdateListener to your animation and call getAnimatedValue. Once you set the view with the new value, its setter call automatically invalidates the view so that it redraws on the screen with the updated value. If you use a different call, you will have to call view.invalidate() manually, or if changing its layout, call requestLayout() on its parent. Note that the default interpolator is the AccelerateDecelerateInterpolator, so we will update it to use the LinearInterpolator instead.

public void doValueAnimator(){
	ValueAnimator animation= ValueAnimator.ofFloat(mLittleChef.getX(),mLittleChef.getX()/2,mLittleChef.getX());
	animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
		@Override
		public void onAnimationUpdate(ValueAnimator animation) {
			Float value = (Float) animation.getAnimatedValue();
			mLittleChef.setX(value);
			//setX() calls this for us: mLittleChef.invalidate();
		}
	});
	animation.addListener(new AnimatorListenerAdapter(){
		@Override
		public void onAnimationEnd(Animator animation) {
			simpleLock= false;
		}
	});
	//default interpolator is the AccelerateDecelerateInterpolator, so we will change this
	animation.setInterpolator(new LinearInterpolator());
	animation.setDuration(mShortAnimationDuration);
	animation.start();
}

b. Object Animation

To define your ObjectAnimator, you need to give it an object and a property of the object in addition to the values you wish to animate between. The property must have a setter method for the ObjectAnimator to call. Remember that the view setters automatically call invalidate to redraw the view, so you don’t have to call it yourself. As ObjectAnimator extends the ValueAnimator class, we will also need to set the interpolator manually to be linear.

public void doObjectAnimator(){
	ObjectAnimator animation= ObjectAnimator.ofFloat(mLittleChef, "x", mLittleChef.getX(),mLittleChef.getX()/2,mLittleChef.getX());
	animation.addListener(new AnimatorListenerAdapter(){
		@Override
		public void onAnimationEnd(Animator animation) {
			simpleLock= false;
		}
	});
animation.setInterpolator(new LinearInterpolator());
	animation.setDuration(mShortAnimationDuration);
	animation.start();
}

c. Object Animation with Keyframes

You can also do object animation with keyframes instead using a PropertyValuesHolder. Keyframes are like snapshots in time of how your object will look. When you give these to the PropertyValuesHolder, it will interpolate the values between the keyframes automatically. The advantage of using keyframes over the other ways of animation is that you can set the time between them much more easily and in a much more condensed way. Instead of defining several animations with different times, you can create one ObjectAnimator and piece-meal out the time between the different keyframes accordingly.

public void doObjectAnimatorKeyframes(){
	float startingPoint= mLittleChef.getX();
	Keyframe kf0 = Keyframe.ofFloat(0f, startingPoint);
	Keyframe kf1 = Keyframe.ofFloat(.5f, startingPoint/2);
	Keyframe kf2 = Keyframe.ofFloat(1f, startingPoint);
	PropertyValuesHolder trans = PropertyValuesHolder.ofKeyframe("x", kf0, kf1, kf2);
	ObjectAnimator animation1 = ObjectAnimator.ofPropertyValuesHolder(mLittleChef, trans);
	animation1.addListener(new AnimatorListenerAdapter(){
		@Override
		public void onAnimationEnd(Animator animation) {
			simpleLock= false;
		}
	});
animation1.setInterpolator(new LinearInterpolator());
	animation1.setDuration(mShortAnimationDuration);
	animation1.start();
}

d. Object Animation with XML

The third way of doing object animation is by using an XML to hold all the values. The XML file should be placed in res/animator; you can place it in res/anim but the prior is more optimal and recommended. While this does separate it out from the code to make a more easily editable and sharable resource file, you are limited by it at the same as the values are static and hard coded in pixel values. This can make it tricky to do a translation animation as the screen size of the device is not always going to be the same and you need to set the ‘valueTo’ animate to. If your animation is going to stay in one place and stay relatively the same size, then you won’t have those concerns.  To account for the different sized devices, you can make separate xml files for the various sizes of devices and place them in the appropriate folder that ends in -<size> e.g. res/animator-large. Also make sure to use values that end in dp to scale for pixel density of the device. (For more on handling different screen sizes see the resources links at the end). This doesn’t quite help in our case though as we don’t know the value to animate back to. We could create our own property with a setter and getter method that will move it to a certain percent value of the screen, but we already have a number of easier animation options to choose from in our case.

public void doObjectAnimatorXML(){
	AnimatorSet object = (AnimatorSet) AnimatorInflater.loadAnimator(getActivity(),R.animator.property_animator);
	object.addListener(new AnimatorListenerAdapter(){
		@Override
		public void onAnimationEnd(Animator animation) {
			simpleLock= false;
		}
	});
	object.setTarget(mLittleChef);
	object.start();
}

And the property_animator.xml from res/animator-xlarge:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" android:ordering="sequentially" >
        <objectAnimator
            android:interpolator="@android:anim/linear_interpolator"
            android:propertyName="x"
            android:duration="1000"
            android:valueTo="138"
            android:valueType="floatType"/>
        <objectAnimator
            android:interpolator="@android:anim/linear_interpolator"
            android:propertyName="x"
            android:duration="1000"
            android:valueTo="276"
            android:valueType="floatType"/>
</set>

e. View Property Animation

Here you can animate directly with the view by using the view.animate() method. You have the option to chain different simultaneous animations together making the code very compact, but you can’t chain the same type of animation together. Thus your animation has to become nested if you want one to happen after the other. Another thing of note is that when reading the documentation for translationX method it says “will cause the View's translationX property to be animated to the specified value”. This is only half of the story though, and so you have to look at the translationX property itself for clarification: “The horizontal position of this view relative to its left position, in pixels.” Hence translationX method will not animate to the absolute position, but to the specified value relative to the view’s left position which is considered as ‘0,0’. For example, if your view is in the original position and you want to move it to ‘original+2’, you should put ‘2’ in as the value and then to move it back use the value ‘0’.

 public void doViewPropertyAnimator(){
	//Can chain other (non translationX) simultaneous animations in here
mLittleChef.animate().translationX(-mLittleChef.getX()/2)                    					.setDuration(mShortAnimationDuration/2)        
 		.setInterpolator(new LinearInterpolator())    	
		.setListener(new AnimatorListenerAdapter() {          
		@Override                 
		public void onAnimationEnd(Animator animation) {  
			//Do the animation back to the start point
			mLittleChef.animate().translationX(0)
			.setDuration(mShortAnimationDuration/2)            	
				.setListener(new AnimatorListenerAdapter() {                 
				@Override                 
				public void onAnimationEnd(Animator animation) {                     
					simpleLock= false;
				}             
			});
		}             
	}); 
}

Summary

Now you have seen the basic setup of our application we are adding animation to and different ways of doing property animation. Property animation is best for animating values of the properties of your object which you can then apply to your object itself. Stay tuned for my second blog of this series which will explore view animations.  This series is continued in part 2.

References

http://developer.android.com/guide/topics/graphics/index.html
http://developer.android.com/training/basics/supporting-devices/screens.html
http://developer.android.com/guide/practices/screens_support.html

About the Author

Whitney Foster is a software engineer at Intel in the Software Solutions Group working on scale enabling projects for Android applications.

Copyright © 2014 Intel Corporation. All rights reserved.
*Other names and brands may be claimed as the property of others.
**This sample source code is released under the Intel Sample Source Code License Agreement