Wednesday, January 7, 2009

Chaining two animations, or how to repeat animationset...

Yesterday I tried to chain two animations !

Actually, what I wanted was, on my high-score list view : translate the listview out of the screen, change the listview content ( for a different type of game ), and retranslate the new listview in the screen.
But, reading the Android-dev mailing list, I realized that it could also interest people using an animationSet.
Apparently, a bug prevent them from handling the repeat flag correctly( see here )
(Note that in my case, I couldn't use an animationSet, because I want to have an operation between the two animations ).

So I first use a AnimationListener to detect the end of the first animation, and launch there the second animation, and...
Kaboum... it ... failed !!
I had discovered a second bug related to animation ( or at least an undocumented constraint ) : you can't launch an animation in the OnAnimationEnd method of the AnimationListener.
I guess after the OnAnimationEnd, there is something like a ClearAnimation done on the view, that remove both the old and the new ones !

So the solution here is to post a message to launch the second animation on the next trame.
So here's my solution :

public class ScorePage extends ListActivity
{
class LocalAnimationListener implements AnimationListener
{
public void onAnimationEnd(Animation animation)
{
//LaunchInAnimation( ); // FAIL:the animation is not launched
//runOnUiThread( mLaunchSecondAnimation ); // FAIL : the runnable is launched immediatly, so like previous method !
//Handler curHandler = new Handler(); // OK, BUT :
//curHandler.post( mLaunchSecondAnimation); // create an unnecessary object !
getListView().post(mLaunchSecondAnimation); // OK : GOOD method !
}
public void onAnimationRepeat(Animation animation)
{
}
public void onAnimationStart(Animation animation)
{
}
};
private Runnable mLaunchSecondAnimation = new Runnable()
{
public void run()
{
LaunchInAnimation();
}
};
LocalAnimationListener MyAnimationListener = new LocalAnimationListener();

public void LaunchInAnimation()
{
// Change the listView Content :
mCurrentHiScore -=1;
if ( mCurrentHiScore < 0 )
mCurrentHiScore = 2;
ListAdapter curAdapter = mScore.GetScoreListAdapter(ScorePage.this, R.layout.score_entry, mCurrentHiScore);
if ( curAdapter != null )
setListAdapter( curAdapter );

// Launch the second animation :
Animation SlideAnim;
SlideAnim = AnimationUtils.loadAnimation(this, R.anim.slide_in_left);
ListView lv = getListView();
lv.startAnimation(SlideAnim);
}

public void LaunchOutAnimation()
{
Animation SlideAnim;
SlideAnim = AnimationUtils.loadAnimation(this, R.anim.slide_out_left);
SlideAnim.setAnimationListener( MyAnimationListener );
ListView lv = getListView();
lv.startAnimation(SlideAnim);
}
(... )


Now it's just fine !

2 comments:

Anonymous said...

Thank you so much. It really helped with Android 2.1, although in Android 4.0.2 it did work for me to launch animations in the onAnimationEnd method.

Prakash Nadar said...

You can use AnimationSet and defer each animation by time the previous animation would take to finish. Simple and better than using runnables.