6-Feb-09 (Created: 6-Feb-09) | More in 'Android Animations'

view animation

In the first two sections of this chapter we have covered Frame by Frame animation and the Layout based animation. In this section we will talk about "View Animation". View animation allows you to animate any arbitrary view by manipulating the transformation matrix that is in place for displaying the view. We will start this section by giving you a brief introduction to View animation. We will then show you code for a test harness to experiment with view animation. Then we will show you a few view animation examples. View animations also include the use of a Camera object. This is "Camera" is distinct from the physical camera on the device. This is purely a graphics concept. We will explain how you can use Camera in association with animation. We will also explain in sufficent depth working with transformation matrices.

What is View Animation

When a View is displayed in Android on a presentation surface the view goes through a tranformation matrix. In graphics applications transformation matrices are used to trasform a view in some aspect. The process involes taking the input pixel coordinates and color combination and translate them to a new set of coordinates and color combinations. At the end of a transformation you will see an altered pictures in terms of size, position, orientation, or color. All of these transformations can be achieved mathematically by taking the input set of coordinates and multiplying them in some manner using a transformation matrix to arrive at a new set of coordinates. This multiplication matrix is called the "transformation matrix". Every view has one of these associated with it or at least will through one of these matrices before getting transferred to a screen. By changing the transformation matrix you can impact how a view would look like. The matrix that doesn't change the view at all is called an "identity" matrix. You typically start with an identity matrix and apply a series of transfromations involving size, position, orientation, and color. You will take the end matrix and use that matrix for drawing the view.

Android exposes the transformation matrix for a view by allowing you to register an animation object with that view. The animation object will have call back where it can obtain the current matrix in place for a view and change it in some manner to arrive at a new view that is transformed. We will go through this process in this section.

View Animation Test Harness

Let us start by planning an example for animating a view. We will start with an activity where we would place a ListView with a few items. We will create a button on top of this list view to start the animation of the List View. When we initially run the program it would look like the following where the button is displayed and also the List View. Figure-5 View Animation Activity

In this animation example, when we click on the button we want to start the view as very small in the middle of the screen and then gradually become bigger as it finally takes all the space that is allocated for it. Let us go ahead and write the code to make this happen.

Here is the sample layout xml you can use for the activity


<?xml version="1.0" encoding="utf-8"?>
<!-- This file is at /res/layout/list_layout.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    
<Button 
   android:id="@+id/btn_animate"
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="Start Animation"
/>

    <ListView
        android:id="@+id/list_view_id"
        android:persistentDrawingCache="animation|scrolling"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        />
</LinearLayout>

Notice that the file location and the file name is embedded at the top of the xml file for your reference. This layout has two parts. The firs is the button that is named "btn_animate" to animate a view. The second control is the list view which is named as "list_view_id".

Now that we have the layout for our activity let us go ahead and create the activity to show the view and also setup the button to start the animation. Here is the code for it.


public class ViewAnimationActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.list_layout);
        setupListView();
        this.setupButton();
    }
    private void setupListView()
    {
         String[] listItems = new String[] {
               "Item 1", "Item 2", "Item 3",
               "Item 4", "Item 5", "Item 6",
         };
         
         ArrayAdapter listItemAdapter = 
             new ArrayAdapter(this
                     ,android.R.layout.simple_list_item_1
                     ,listItems);
         ListView lv = (ListView)this.findViewById(R.id.list_view_id);
         lv.setAdapter(listItemAdapter);
    }
    private void setupButton()
    {
       Button b = (Button)this.findViewById(R.id.btn_animate);
       b.setOnClickListener(
           new Button.OnClickListener(){
             public void onClick(View v)
             {
                //animateListView();
             }
           });
    }
}

This code is very similar to the example activity we have shown for the Layout Animation. In a similar manner we have loaded the view and set up the list view so that it has 6 text items in it. The button is setup in such a way that it would call "animateListView()" when clicked. For now comment this out until we get this basic example running.

you will be able to invoke this activity as soon as you register this activity in the manifest file. Here is the code for it.


<activity android:name=".ViewAnimationActivity"
        android:label="View Animation Test Activity">

Once this registration is in place you can invoke this activity from any menu item you may have in your application by executing the following code


Intent intent = new Intent(this, ViewAnimationActivity.class);
startActivity(intent);

When you run this program at this time you will see the UI as laid out in Figure 8-5

Adding Animation

Our aim in this example is to add animation to the ListView. To do that we need to have a class that derives from "android.view.animation.Animation" and override the "applyTransformation" method to modify the transformation matrix. Let us call this class "ViewAnimation". Once we have this class we can do something like this on the ListView class


     ListView lv = (ListView)this.findViewById(R.id.list_view_id);
     lv.startAnimation(new ViewAnimation());

Let us go ahead and show you the source code for "ViewAnimation" and discuss the kind of animation we want to accomplish


public class ViewAnimation extends Animation 
{
    public ViewAnimation2(){}

    @Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);
        setDuration(2500);
        setFillAfter(true);
        setInterpolator(new LinearInterpolator());
    }
    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) 
   {
        final Matrix matrix = t.getMatrix();
        matrix.setScale(interpolatedTime, interpolatedTime);
    }
}

The "initialize" method is a callback method that tells us about the dimensions of the view. This is also a place to initialize the animation parameters. In this example we have set the duration to be 2.5 seconds. We have also set the effect of animation to be in tact after the completion of the animation by setting the "fill" after to true. We have also indicated that the interpolator is a linear interpolator where animation changes in a gradual manner from start to finish.

The main part of the animation is happening in "applyTransformation". This method is going to be called again and again by the Android framework to simulate animation. Everytime it is called we have a diffrent value for the "interpolatedTime". This parameter will change from 0 to 1 depending at what point of time we are in the "2.5" second duration. When "interpolatedTime" is "1" we are at the end of the animation.

Our goal then is to change the transformation matrix that is available through the transformation object called "t" in this method. We will first get the matrix and change something about it. When the view gets painted the new matrix will take affect. You can find the kind of methods available on the "Matrix" object by looking up the api documentation for "android.graphics.Matrix" available at the following URL


http://code.google.com/android/reference/android/graphics/Matrix.html

In the example here the code that changes the matrix is


        matrix.setScale(interpolatedTime, interpolatedTime);

The "setScale" method takes two parameters, the scaling factor on the x direction and the scaling factor in the y direction. Because the "interpolatedTime" goes between 0 and 1, we can use that value directly as the scaling factor. So when we start the animation the scaling factor is 0 in both x and y directions. Half way through the animation this value will be 0.5 in both x and y directions. At the end of the animation the view will be at its full size because the scaling factor would be "1" in both x and y directions. The end result of this animation is that the ListView starts out tiny and grows to take the full size.

Here is the complete source code in one place for the ViewAnimationActivity that includes the animation


public class ViewAnimationActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.list_layout);
        setupListView();
        this.setupButton();
    }
    private void setupListView()
    {
         String[] listItems = new String[] {
               "Item 1", "Item 2", "Item 3",
               "Item 4", "Item 5", "Item 6",
         };
         
         ArrayAdapter listItemAdapter = 
             new ArrayAdapter(this
                     ,android.R.layout.simple_list_item_1
                     ,listItems);
         ListView lv = (ListView)this.findViewById(R.id.list_view_id);
         lv.setAdapter(listItemAdapter);
    }
    private void setupButton()
    {
       Button b = (Button)this.findViewById(R.id.btn_animate);
       b.setOnClickListener(
           new Button.OnClickListener(){
             public void onClick(View v)
             {
                animateListView();
             }
           });
    }
    private void animateListView()
    {
       ListView lv = (ListView)this.findViewById(R.id.list_view_id);
       lv.startAnimation(new ViewAnimation());
    }
}

When you run this example you will notice an odd thing. The ListView instead of uniformly growing larger from the middle of the screen it will grow larger from the top left corner. The reason for this is the origin for the matrix operations is at the top left corner. To get the desired affect you first have to move the whole view to the center and then apply the matrix and then move it back to the previous center.

The code for doing this is as below


        final Matrix matrix = t.getMatrix();
        matrix.setScale(interpolatedTime, interpolatedTime);
        matrix.preTranslate(-centerX, -centerY);
        matrix.postTranslate(centerX, centerY);

The two methods "preTranslate" and "postTranslate" sets up a matrix before the scale operation and after the scale operation. This is equivalent to making three matrix transformation in tandem. The code


        matrix.setScale(interpolatedTime, interpolatedTime);
        matrix.preTranslate(-centerX, -centerY);
        matrix.postTranslate(centerX, centerY);

is equivalent to


move to a different center
scale it
move to the original center 		

Here is the code that will give us the desired affect


protected void applyTransformation(float interpolatedTime, Transformation t) 
{
	final Matrix matrix = t.getMatrix();
	matrix.setScale(interpolatedTime, interpolatedTime);
	matrix.preTranslate(-centerX, -centerY);
	matrix.postTranslate(centerX, centerY);
}

You will see this pattern of "pre" and "post" applied again and again. There are otherways of acomplishing the same with other methods available on the Matrix class. We will cover this little later towards the end of this chapter. More importantly the "Matrix" class allows you not only to scale a view but also to move it around through "translate" methods and also change orientation through "rotate" methods. You can experiment with these methods and see how the animation look like. In fact the animations we have tried in the previosus Layout Animation section are all implemented internally using the methods on this Matrix class.

Using Camera to provide depth perception in 2D

The graphics package in Android provides another animation or more accurately transformation related class called "Camera". This class is used to provide depth perception by projecting a 2D image that is moving in 3D on to a 2D surface. For example, we can take the ListView above and move it back by 10 pixels and rotate it by 30 degrees around the "Y" axis. Here is an example of manipulating the matrix using a Camera.


...
Camera camera = new Camera();
..
protected void applyTransformation(float interpolatedTime, Transformation t) 
{
	final Matrix matrix = t.getMatrix();
	camera.save();
	camera.translate(0.0f, 0.0f, (1300 - 1300.0f * interpolatedTime));
	camera.rotateY(360 * interpolatedTime);
	camera.getMatrix(matrix);
	
	matrix.preTranslate(-centerX, -centerY);
	matrix.postTranslate(centerX, centerY);
	camera.restore();
}

The code here is animating the ListView by placing the view first 1300 pixels back on the "z" axis and bringing it back to the plane where Z is "0". While it is doing this, it is also rotating the view starting from 0 degrees to 360 degrees. Let us see how the code relates to this behavior. The following method


	camera.translate(0.0f, 0.0f, (1300 - 1300.0f * interpolatedTime));

This is telling the camera to translate the view such that when "interpolatedTime" is zero or at the begining of the animation, the "z" value will be 1300. As the animation progresses the "z" value will be getting smaller and smaller until at the end the interpolatedTime becomes 1 and the "z" value will be 0.

The following method


	camera.rotateY(360 * interpolatedTime);

is taking advantage of 3D rotation around an axis by the camera. At the begining of the animation it will be 0. At the end of the animation it will be 360.

The following method


	camera.getMatrix(matrix);

takes the operations performed on the Camera so far and imposes those operations on the matrix that is passed in. Once we do that the "matrix" has the equivalent translations needed to get the end effect of having a camera. Camera is out of the picture now, just the matrix has all the operations embedded in it. Then we do the "pre" and "post" on the matrix to shift the center and bring it back. At the end we set the Camera to its original state that was saved earlier.

when you plug this code in the example you will see the ListView arriving from the center of the view in a spinning manner toward the front of the screen.

Animation Listener class

You can listen to the animation start and end events by implementing AnimationListener class and setting the implementation against the animation class. Here is an example of an animation listener class.


public class ViewAnimationListener 
implements Animation.AnimationListener {

    private ViewAnimationListener(){}

    public void onAnimationStart(Animation animation) 
    {
    	Log.d("Animation Example", "onAnimationStart");
    }
    public void onAnimationEnd(Animation animation) 
    {
    	Log.d("Animation Example", "onAnimationEnd");
    }
    public void onAnimationRepeat(Animation animation) 
    {
    	Log.d("Animation Example", "onAnimationRepeat");
    }
}

This class just logs messages. You can update the "animateListView" method in the view animation example as below to take into account the animation listener.


private void animateListView()
{
   ListView lv = (ListView)this.findViewById(R.id.list_view_id);
   ViewAnimation animation = new ViewAnimation();
   animation.setAnimationListener(new ViewAnimationListener()):
   lv.startAnimation(animation);
}

Some notes on Transformation Matrices

As you have seen in this chapter, matrices are key to transforming views and animations. We will spend some time exploring some key methods of this class. The primary operations on a matrix are


matrix.reset();
matrix.setScale();
matrix.setTranslate()
matrix.setRotate();
matrix.setSkew();

The first one resets a matrix to what is called an "identity" matrix which causes no change to the view when applied. "setScale" is responsible for changing the size. "setTranslate" is responsible for changing the position simulating movement. "setRotate" is responsible for changing orientation. "setSkew" is responsible for distorting a view.

Matrices can be concatenated or multiplied together to get the compound affect of individual transformations. Consider the following example where m1, m2, and m3 are identity matrices,


m1.setScale();
m2.setTranlate()
m3.concat(m1,m2)

Transforming a view by "m1" first and then transforming the resulting view with "m2" is equivlaent to transforming the view by "m3". Also notice that "set" methods will replace the previous transformations. Also understand that m3.concat(m1,m2) is different from m3.concat(m2,m1).

You have already seen the pattern used by "preTranslate" and "postTranslate" method. You have versions of "pre" and "post" for each "set" transformation method. Ultimately a preTranslate


m1.preTranslate(m2)

is equivalent to


m1.concat(m2,m1)

m1.postTransalte(m2)

is equivalent to


m1.concat(m1,m2)

by extension the code


        matrix.setScale(interpolatedTime, interpolatedTime);
        matrix.preTranslate(-centerX, -centerY);
        matrix.postTranslate(centerX, centerY);

is equivalent to


Matrix matrixPreTranslate = new Matrix();
matrixPreTranslate.setTranslate(-centerX, -centerY);

Matrix matrixPreTranslate = new Matrix();
matrixPostTranslate.setTranslate(cetnerX, centerY);

matrix.concat(matrixPreTranslate,matrix);
matrix.postTranslate(matrix,matrixpostTranslate);