Android 4
19. Animation - Frame By Frame, Layout, and View : Part B
This chapter
- 19.0 Animation
- 19.1 Frame By Frame Animation
- 19.2 Layout Animation
- 19.3 View Animation
- 19.4 View - 3D Transition
Android exposes the transformation matrix for a view by allowing us to register an animation object with the view. The animation object will have a callback that lets it obtain the current matrix for a view and change it in some manner to arrive at a new view.
Let's start by planning an example for animating a view. We'll begin with an activity where we'll place a ListView with a few items. Then, we will create a button at the top of the screen to start the ListView animation when clicked.
Let's look at the layout file, /res/list_layout.xml
<?xml version="1.0" encoding="utf-8"?> <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>
The layout has two parts: the first is the button named btn_animate to animate a view, and the second part is the ListView, which is named list_view_id.
Now we have the layout for the activity, we can create the activity to show the view:
ViewAnimation.java
package com.bogotobogo.viewanimation; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ListView; public class ViewAnimation 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<String> listItemAdapter = new ArrayAdapter<String>(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); ViewAnim animation = new ViewAnim(); lv.startAnimation(animation); } }
We have loaded the view and set up the ListView to contain six text items. We've set up the button in such a way that it would call animate() when clicked.
We can invoke this activity as long as we register it in the AndroidManifest.xml file:
<activity android:name=".ViewAnimation" android:label="@string/app_name">
We want to add animation to the ListView. So, we need a class that derives from android.view.animation.Animation. We then need to override the applyTransformation method to modify the transformation matrix.
Let's call this derived class ViewAnim
ViewAnim.java
package com.bogotobogo.viewanimation; import android.graphics.Matrix; import android.view.animation.Animation; import android.view.animation.LinearInterpolator; import android.view.animation.Transformation; public class ViewAnim extends Animation { public void 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); } }
Once we have the ViewAnim class, we can do:
ListView lv = (ListView)this.findViewById(R.id.list_view_id); ViewAnim animation = new ViewAnim();
as in the animateListView() method.
Let's look at the class more carefully.
The initialize() method ia s callback method that tells us about the dimensions of the view. This is also a place to initialize any animation parameters we might have.
The main part of the animation occurs in the applyTransformation() method. The Android framework will call this method again and again to do animation. Every time Android calls the method, interpolatedTime has a different value. This parameter changes from 0 to 1 depending on where we are in the 2.5 second duration that we set during initialization.
We want to change the transformation matrix that is available through the transformation object called t in the applyTransformation() method. We will first get the matrix and change something about it. When the view gets painted, the new matrix will take effect.
The setScale() method takes two parameters: the scaling factor in the x direction and the scaling factor in the y direction.
matrix.setScale(interpolatedTime, interpolatedTime);
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.
Here are some snapshots during the animation.
Files used in this View Animation example, ViewAnimation.zip
This example shows how to use layout animation and various transformations on views. The result is a 3D transition between a ListView and an ImageView.
When the user clicks the list, it flips to show the picture. When the user clicks the picture, it flips to show the list.
The animation is made of two smaller animations:
- rotates the list by 90 degrees on the Y axis and the second half rotates the picture by 90 degrees on the Y axis.
- After that, the list is made invisible and the picture is set visible.
The animation that rotates the view on the Y axis between two specified angles.
This animation also adds a translation on the Z axis (depth) to improve the effect.
Here are our Java codes,
package com.bogotobogo.swap3d; import android.app.Activity; import android.os.Bundle; import android.widget.ListView; import android.widget.ArrayAdapter; import android.widget.AdapterView; import android.widget.ImageView; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; public class Swap3D extends Activity implements AdapterView.OnItemClickListener, View.OnClickListener { private ListView mPhotosList; private ViewGroup mContainer; private ImageView mImageView; // Names of the photos we show in the list private static final String[] PHOTOS_NAMES = new String[] { "Brasil", "Arctic", "Antarctica", "Mongolia", "Madagascar" }; // Resource identifiers for the photos we want to display private static final int[] PHOTOS_RESOURCES = new int[] { R.drawable.p1, R.drawable.p2, R.drawable.p3, R.drawable.p4, R.drawable.p5 }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mPhotosList = (ListView) findViewById(android.R.id.list); mImageView = (ImageView) findViewById(R.id.picture); mContainer = (ViewGroup) findViewById(R.id.container); // Prepare the ListView final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, PHOTOS_NAMES); mPhotosList.setAdapter(adapter); mPhotosList.setOnItemClickListener(this); // Prepare the ImageView mImageView.setClickable(true); mImageView.setFocusable(true); mImageView.setOnClickListener(this); // Since we are caching large views, we want to keep their cache // between each animation mContainer.setPersistentDrawingCache(ViewGroup.PERSISTENT_ANIMATION_CACHE); } /** * Setup a new 3D rotation on the container view. * * @param position the item that was clicked to show a picture, or -1 to show the list * @param start the start angle at which the rotation must begin * @param end the end angle of the rotation */ private void applyRotation(int position, float start, float end) { // Find the center of the container final float centerX = mContainer.getWidth() / 2.0f; final float centerY = mContainer.getHeight() / 2.0f; // Create a new 3D rotation with the supplied parameter // The animation listener is used to trigger the next animation final Rotation rotation = new Rotation(start, end, centerX, centerY, 310.0f, true); rotation.setDuration(500); rotation.setFillAfter(true); rotation.setInterpolator(new AccelerateInterpolator()); rotation.setAnimationListener(new DisplayNextView(position)); mContainer.startAnimation(rotation); } public void onItemClick(AdapterView parent, View v, int position, long id) { // Pre-load the image then start the animation mImageView.setImageResource(PHOTOS_RESOURCES[position]); applyRotation(position, 0, 90); } public void onClick(View v) { applyRotation(-1, 180, 90); } /** * This class listens for the end of the first half of the animation. * It then posts a new action that effectively swaps the views when the container * is rotated 90 degrees and thus invisible. */ private final class DisplayNextView implements Animation.AnimationListener { private final int mPosition; private DisplayNextView(int position) { mPosition = position; } public void onAnimationStart(Animation animation) { } public void onAnimationEnd(Animation animation) { mContainer.post(new SwapViews(mPosition)); } public void onAnimationRepeat(Animation animation) { } } /** * This class is responsible for swapping the views and start the second * half of the animation. */ private final class SwapViews implements Runnable { private final int mPosition; public SwapViews(int position) { mPosition = position; } public void run() { final float centerX = mContainer.getWidth() / 2.0f; final float centerY = mContainer.getHeight() / 2.0f; Rotation rotation; if (mPosition > -1) { mPhotosList.setVisibility(View.GONE); mImageView.setVisibility(View.VISIBLE); mImageView.requestFocus(); rotation = new Rotation(90, 180, centerX, centerY, 310.0f, false); } else { mImageView.setVisibility(View.GONE); mPhotosList.setVisibility(View.VISIBLE); mPhotosList.requestFocus(); rotation = new Rotation(90, 0, centerX, centerY, 310.0f, false); } rotation.setDuration(5000); rotation.setFillAfter(true); rotation.setInterpolator(new DecelerateInterpolator()); mContainer.startAnimation(rotation); } } }
and Rotation.java:
package com.bogotobogo.swap3d; import android.view.animation.Animation; import android.view.animation.Transformation; import android.graphics.Camera; import android.graphics.Matrix; public class Rotation extends Animation { private final float mFromDegrees; private final float mToDegrees; private final float mCenterX; private final float mCenterY; private final float mDepthZ; private final boolean mReverse; private Camera mCamera; /** * Creates a new 3D rotation on the Y axis. The rotation is defined by its * start angle and its end angle. Both angles are in degrees. The rotation * is performed around a center point on the 2D space, definied by a pair * of X and Y coordinates, called centerX and centerY. When the animation * starts, a translation on the Z axis (depth) is performed. The length * of the translation can be specified, as well as whether the translation * should be reversed in time. * * @param fromDegrees the start angle of the 3D rotation * @param toDegrees the end angle of the 3D rotation * @param centerX the X center of the 3D rotation * @param centerY the Y center of the 3D rotation * @param reverse true if the translation should be reversed, false otherwise */ public Rotation(float fromDegrees, float toDegrees, float centerX, float centerY, float depthZ, boolean reverse) { mFromDegrees = fromDegrees; mToDegrees = toDegrees; mCenterX = centerX; mCenterY = centerY; mDepthZ = depthZ; mReverse = reverse; } @Override public void initialize(int width, int height, int parentWidth, int parentHeight) { super.initialize(width, height, parentWidth, parentHeight); mCamera = new Camera(); } @Override protected void applyTransformation(float interpolatedTime, Transformation t) { final float fromDegrees = mFromDegrees; float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime); final float centerX = mCenterX; final float centerY = mCenterY; final Camera camera = mCamera; final Matrix matrix = t.getMatrix(); camera.save(); if (mReverse) { camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime); } else { camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime)); } camera.rotateY(degrees); camera.getMatrix(matrix); camera.restore(); matrix.preTranslate(-centerX, -centerY); matrix.postTranslate(centerX, centerY); } }
Layout file is /res/layout/:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/container" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ListView android:id="@android:id/list" android:persistentDrawingCache="animation|scrolling" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layoutAnimation="@anim/layout_bottom_to_top_slide" /> <ImageView android:id="@+id/picture" android:scaleType="fitCenter" android:layout_width="fill_parent" android:layout_height="fill_parent" android:visibility="gone" /> </FrameLayout>
And the two XML files for animation, /res/anim/layout_bottom_to_top_slide.xml:
<?xml version="1.0" encoding="utf-8"?> <layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android" android:delay="30%" android:animationOrder="reverse" android:animation="@anim/slide_right" />
and /res/anim/slide_right.xml:
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator"> <translate android:fromXDelta="-100%p" android:toXDelta="0" android:duration="@android:integer/config_shortAnimTime" /> </set>
Files used in this View Animation example, Swap3D.zip
Previous sections:
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization