Pages

Jumat, 18 Mei 2012

How to use Android animation listeners

Takeaway: This tutorial shows how to use Android animation listeners when simple time-based callbacks aren’t enough and XML-based animation sets get too hairy.

I enjoy working with Android’s animation framework, as you might have guessed by reading my tips on creating a custom “spinner”, chaining animations, and using the ViewFlipper widget. It’s still a bit loosey-goosey when compared to iOS, but that doesn’t make it any less powerful. Also, animations in Android are a lot of fun!
In this post I demonstrate how I use Android animation listeners when simple time-based callbacks aren’t enough and XML-based animation sets get too hairy. You can follow along with the tutorial, or download and import the project.
1. Open Eclipse and start a new Android project targeted at Android 1.6 or higher. Be sure to rename the startup activity Main.java.
2. I’m using a small PNG image I created in GIMP; I call it heartbeat.png, and it’s just a simple gradient (Figure A). In your /res folder, create a /drawable directory and store the image there.
Figure A

3. Next we need to create a new directory in the /res folder called /anim — this is where we place our actual animations. In this instance, I created four XML animations. They are all transformations, and the idea is to move our heartbeat image left to right on the display, with a “blip” in the middle that simulates a heart monitor display at a hospital bedside. I’m not going to go through the animations line by line. If you need to brush up on Android XML-based animations, Google’s developer guide is the presiding authority; you should refer to the section titled View Animation for a rundown on transformations.
xform_left_to_right_begin.xml
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/linear_interpolator"
android:fromXDelta="5%p"
android:toXDelta="50%p"
android:fromYDelta="50%p"
android:toYDelta="50%p"
android:fillBefore="true"
android:duration="900"
android:fillAfter="true"/>
xform_to_peek.xml
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/linear_interpolator"
android:fromXDelta="50%p"
android:toXDelta="60%p"
android:fromYDelta="50%p"
android:toYDelta="30%p"
android:fillBefore="true"
android:duration="200"
android:fillAfter="true"/>
xform_from_peek.xml
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/linear_interpolator"
android:fromXDelta="60%p"
android:toXDelta="70%p"
android:fromYDelta="30%p"
android:toYDelta="50%p"
android:fillBefore="true"
android:duration="200"
android:fillAfter="true"/>
xform_left_to_right_end.xml
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/linear_interpolator"
android:fromXDelta="70%p"
android:toXDelta="90%p"
android:fromYDelta="50%p"
android:toYDelta="50%p"
android:fillBefore="true"
android:duration="400"
android:fillAfter="true"/>
4. The last resource we need to get in place is our layout. Inside the /res/layout folder, add a main.xml file. The contents are nothing more than a text view and an image view tucked inside of a linear layout.
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:padding="25dip"
android:textColor="#ffffff"
android:text="Animation Listener Demo"
android:layout_gravity="center"
android:gravity="center"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/heartbeat"
android:id="@+id/blip"/>
</LinearLayout>
5. We are ready for our /src folder and the Main.java, which sits behind our layout. We want to extend activity and implement animation listener. We will also want a couple of class-level variables.
Main.java
package com.authorwjf.beep;
import android.app.Activity;
import android.os.Bundle;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Animation.AnimationListener;
import android.widget.ImageView;
public class Main extends Activity implements AnimationListener {
private int state_machine = 0;
private Animation mAnim = null;
}
6. Next we will need to override both the on create and the activity on stop. If you don’t handle the on stop case, you can expect the app to behave unpredictably when there is an interruption like a phone call.
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mAnim = AnimationUtils.loadAnimation(this, R.anim.xform_left_to_right_begin);
mAnim.setAnimationListener(this);
ImageView img = (ImageView)findViewById(R.id.blip);
img.clearAnimation();
img.setAnimation(mAnim);
img.startAnimation(mAnim);
}
@Override
protectedvoid onStop() {
super.onStop();
try {
ImageView img = (ImageView)findViewById(R.id.blip);
img.clearAnimation();
mAnim.setAnimationListener(null);
} catch (Exception e) {
//log
}
}
7. The final step is to implement the animation listener callback. In this instance, I’m only interested in loading a new animation when the current one ends. However, we are still required to override both the on start and on repeat functions.
@Override
publicvoid onAnimationEnd(Animation a) {
a.setAnimationListener(null);
switch (state_machine) {
case 0:
a = AnimationUtils.loadAnimation(this, R.anim.xform_to_peek);
state_machine=1;
break;
case 1:
a = AnimationUtils.loadAnimation(this, R.anim.xform_from_peek);
state_machine=2;
break;
case 2:
a = AnimationUtils.loadAnimation(this, R.anim.xform_left_to_right_end);
state_machine=3;
break;
case 3:
a = AnimationUtils.loadAnimation(this, R.anim.xform_left_to_right_begin);
state_machine=0;
break;
}
a.setAnimationListener(this);
ImageView img = (ImageView)findViewById(R.id.blip);
img.clearAnimation();
img.setAnimation(a);
img.startAnimation(a);
}
@Override
public void onAnimationRepeat(Animation arg0) {
}
@Override
public void onAnimationStart(Animation arg0) {
}
At this point, the app should be ready to compile and run (Figure B). If you have an actual Android device, I recommend trying it on the physical hardware. It runs on the emulator, but the animations tend to be a bit jerky.
Figure B

After you’ve had a chance to run it as is, take a few minutes to go back and play with the timing variables in the /anim folder. You can produce some pretty cool effects. I think you’ll find at the end of the day, Android’s animation listener is one more arrow you can tuck inside your digital quiver.