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:
- You must have a
Viewthat is going to run an animated
Drawable(say, on click).
- You must change the visibility of a parent
Viewbefore 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
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:
And the result will be correct:
If you want to play around with this problem, check out the sample.