Friday, March 8, 2013

Glow effect

The task to accomplish is to make a bit of colour animation on a button (or practically any view), when a defined event happens (lats say, when a "flying" list item flies to it). The animation should be a growing-then-declining glow effect.
So the background should be animated from the current state (which is #ff333333)
to change its color to #ff3355dd within 200ms
than change its color to #ff333333 again within 200ms.

Using Android Honeycomb this can easily done with property animation:
private static final int GLOW_ANIM_DURATION = 400;
    private void tabGlowAnimation(View targetTab) {
        final ObjectAnimator objAnim =
                                        "backgroundColor", // we want to modify the backgroundColor
                                        new ArgbEvaluator(), // this can be used to interpolate between two color values
                                        targetTab.getContext().getResources().getColor(R.color.tab_background), // start color defined in resources as #ff333333
                                        targetTab.getContext().getResources().getColor(R.color.tab_glow) // end color defined in resources as #ff3355dd
        objAnim.setDuration(GLOW_ANIM_DURATION / 2);
        objAnim.setRepeatMode(ValueAnimator.REVERSE); // start reverse animation after the "growing" phase

This is very convenient. However it still has 2 problems:
  • It doesn't work on preHC devices
  • the start color is predefined during coding time (which is not a problem in this case). It is not easy to determine the color of the background, because the background is a drawable and not a color at this point. And/or you needed to implement a custom Evaluator

Unfortunately on earlier devices it is - lets say - challenging to implement a same effect. My solution is an intermittent view layer between the foreground and background of the button. This view has a custom drawable, which alpha value is animated as required. Actually with this we can have any fancy glow (circular, tiger shaped, etc.). I used an idea found in

I've seen this post:, but since I tried to use it on a tabview, it messed up the layout.

public class GlowEffect implements Runnable {
    private static final int MAX_ALPHA = 255;
     * time interval to reschedule a redraw.
    private static final int MIN_TICK_MILLIS = 30;
     * The alpha of the drawable of this view will be adjusted for the glow effect
    private View mGlowLayer;

     * the time, when the animation started. (Or should have started if we restart the glow animation)
    private long mStartTimeMillis;
     * duration of the whole animation = glow increase + glow decrease
    private int mDuration;
     * inner representation of the required alpha value. Used when the animation restarts.
     * We have to store it, because Drawable.getAlpha() doesn't exist.
    private double mAlpha;
     * The time between each animation steps.
    private int mTickMillis;

    public GlowEffect() {

    public void glow(View glowLayer, int durationMillis) {
        mGlowLayer = glowLayer;
        // correct duration time if the animation has been restarted and the previous is still in progress
        // we continue the animation from that point
        final long suspectedTimeFromStart = Math.round(mAlpha * mDuration / 2);
        mDuration = durationMillis;

        mStartTimeMillis = SystemClock.uptimeMillis() - suspectedTimeFromStart;
        mTickMillis = Math.max(MIN_TICK_MILLIS, durationMillis / MAX_ALPHA);
        mGlowLayer.postDelayed(this, mTickMillis);

    public void run() {
        boolean done = true;
        final Drawable who = mGlowLayer.getBackground();

        float normalized = (float) (SystemClock.uptimeMillis() - mStartTimeMillis) / mDuration;
        normalized = Math.min(normalized, 1.0f);
        done = normalized >= 1.0f;

        // linear increase for half of the duration, than linear decrease
        //noinspection MagicNumber
        mAlpha = (0.5 - Math.abs(normalized - 0.5f)) * 2.0f;
        int alpha = (int)Math.round(mAlpha * MAX_ALPHA);

        alpha = Math.max(0, alpha);
        alpha = Math.min(MAX_ALPHA, alpha);


        if (!done) {
            mGlowLayer.postDelayed(this, mTickMillis);
        } else {


Whenever a glow is needed, the glow() should be called.

No comments: