An animated drawable issue in Lollipop

A common pattern in Android is to use View.setVisibility() to swap widgets in your hierarchy. Unfortunately, in Lollipop, there is a risk of running afoul of the new RippleDrawable when doing so.

Let's look at a sample project. It swaps the visibility of two Buttons when they are clicked. Here it is in action:

That's odd... why is the ripple effect displaying when a Button appears, rather than disappears? It seems to be queueing the animation, then only playing it when the Button is visible again. You would expect that when a View becomes invisible the ripple would disappear as well, but it doesn't.

There are two conditions that need to be satisfied to reproduce this issue:

  1. You must have a View that is going to run an animated Drawable (say, on click).
  2. You must change the visibility of a parent View before the animation plays.

It's key (in reproducing this bug) that the visibility of the parent is what changes. Android handles the simple case properly - that is, changing the visibility of the Button itself.

While this sounds esoteric, it's really quite common; anytime you have a button clearing a set of widgets it can happen. For a real-life example, check out Google Keep, which does it when you add/clear a reminder on a note.

Luckily, there is a solution: Drawable.jumpToCurrentState(). It basically fast-forwards any Drawable animation to its final destination. I prefer using ViewCompat.jumpDrawablesToCurrentState(), since it operates on all Drawables associated with a View at once and it's backwards compatible.

When you want to reveal a View which may have an animated Drawable queued up, just use some code like this:

myButtonContainer.setVisibility(View.VISIBLE);
ViewCompat.jumpDrawablesToCurrentState(myButtonContainer);

And the result will be correct:

If you want to play around with this problem, check out the sample.