Wednesday, August 25, 2010

Endless flashing TextView animation in Android

A little while ago I needed a flashing text in Android.


Pretty easy you'd think: stick two animations in an AnimationSet, set the repeatMode to Animation.RESTART and repeatCount to Animation.INFITITE like this:

XML


<set xmlns:android="http://schemas.android.com/apk/res/android"
<alpha
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromAlpha="0.0"
    android:toAlpha="1.0"
    android:duration="1000"
/>
<alpha
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromAlpha="0.9"
    android:toAlpha="0.0"
    android:duration="1000"
    android:startOffset="1000"
/>
</set>



Activity code

AnimationSet c = (AnimationSet) AnimationUtils.loadAnimation(this, R.anim.flash);
c.setRepeatMode(Animation.RESTART);
c.setRepeatCount(Animation.INFINITE);
TextView tv = (TextView) findViewById(R.id.flashingTextView);
tv.clearAnimation();
tv.startAnimation(c);



and you're done.
But no! It only runs one time, or 1.5 times or whatever; definitely not endlessly.

This pretty basic requirement of looping a set of animations indefinitely is apparently a known bug. I haven't been able to find out if it's still an outstanding issue today (August 2010) but unless I implemented it incorrectly, it is definitely present in SDK 1.6.
Based on the answers in that thread and the example code here, I came up with the following solution:

XML
A fadein.xml and a fadeout.xml. See the end of this post for the complete code.

Activity code


    // Get ref to what we're going to animate (fade-in and fade-out indefinitely)
    tv = (TextView) findViewById(R.id.flashingTextView);

    // Setup fadein/out animations
   fadeIn = AnimationUtils.loadAnimation(this, R.anim.fadein);
   fadeIn.setAnimationListener( myFadeInAnimationListener );
   fadeOut = AnimationUtils.loadAnimation(this, R.anim.fadeout);
   fadeOut.setAnimationListener( myFadeOutAnimationListener );
   
   // And start with the fade in
   launchInAnimation();

    public void launchInAnimation() {
       tv.startAnimation(fadeIn);
    }    

    public void launchOutAnimation() {
       // Launch the second animation :
       tv = (TextView) findViewById(R.id.flashingTextView);
       tv.startAnimation(fadeOut);
    }    
    



Two listeners which start the other animation at the end of an animation:

    private class LocalFadeInAnimationListener implements AnimationListener {
       public void onAnimationEnd(Animation animation) {
           tv.post(mLaunchFadeOutAnimation);
        }
       public void onAnimationRepeat(Animation animation) {
       }
       public void onAnimationStart(Animation animation) {
       }
    };
    
    private class LocalFadeOutAnimationListener implements AnimationListener {
       public void onAnimationEnd(Animation animation) {
           tv.post(mLaunchFadeInAnimation);
      }
       public void onAnimationRepeat(Animation animation) {
      }
      public void onAnimationStart(Animation animation) {
      }
    };
    
    private LocalFadeInAnimationListener myFadeInAnimationListener = new LocalFadeInAnimationListener();
    private LocalFadeOutAnimationListener myFadeOutAnimationListener = new LocalFadeOutAnimationListener();




Runnables that do the fading

    private Runnable mLaunchFadeOutAnimation = new Runnable() {
       public void run() {
            launchOutAnimation();
       }
    };    
    
    private Runnable mLaunchFadeInAnimation = new Runnable() {
       public void run() {
        launchInAnimation();
       }
    };    



Note that I improved efficiency of the mentioned example code by only setting the animations one time and getting the TextView to animate only once.

That's it!

Note: in the logcat I do get these messages (2-3 times in about 5 minutes):


08-22 15:33:28.078: WARN/SurfaceComposerClient(252): lock_layer timed out (is the CPU pegged?) layer=0, lcblk=0x41048020, 
state=00000002 (was 00000043)
08-22 15:33:28.078: WARN/SurfaceComposerClient(252): lock_layer() timed out but didn't appear to need to be locked and 
we recovered (layer=0, lcblk=0x41048020, state=00000002)



Pretty strange, the CPU doesn't seem to be pegged... It says it recovers, but I guess the warning is there for a reason. Can the code be modified to avoid these warnings?
It might be a platform bug. Indeed I do see it appear in 1.6 but not in 2.1.

Full code (tested on 1.6) can be downloaded here.

3 comments:

vani said...

how do i give fadein fadeout efffect for multiple images ,i.e one after the other.

Techie said...

@vani: haven't tried that yet, but how I probably would try to do that: have a fade-in/fade-out XML per image, each with a larger startOffset value. Then in the code set one of the animations for each image.
If you want to run that endlessly, you'd have to split the fade-in/out XML and combine it with the stuff in the post. Hope that helps a bit.

Jena said...

This works for me:






Feel free to tweak the durations to get the effect you want.


The bug you're talking about only concerns repeating elements, simple animations like can be repeated endlessly. The trick is to have the repeat mode to "reverse".


Of course it only solves this particular case of blinking animation. Complex animations with more than two "states" will have use AnimationListeners while waiting for a fix.