Minute of Pain #1. ViewProperyAnimator

Or how you can easily forget some things

Michael Spitsin
4 min readMar 24, 2017

Preface: In this series of small articles I will share my seconds of indignation (more rude form is “WTF moments”) when I sat steady, stunned with thoughts like “Why?”, “Guys, come on”, “Where are alternatives?” or something like that. My aim is not to harm creators and maintainers of languages or SDK, it just a…maybe surprising moments for me. Or things that I’ve seen before, but every time I forget about some feature or specialty.

I facing with ViewPropertyAnimator pretty usually, because of their simplicity and concise, subtle API. We can easy start an animation by simply writing:

view.animate().alpha(0).start();

Mostly I create animations similar to above one. For example, we need just to translate view little bit down and then return it back. But sometimes I need to use more complex examples or not symmetrical animations and here we can face with troubles.

Problem

Example 1. Slow fade-in, fast fade-out

Here is a link to the first sample project. We have only one screen with an icon in a center and two buttons on the bottom of the screen: button to show icon and button to hide it. Small comment: I use Kotlin and Kotlin Android Extensions for a more graceful code. Here what we have in code:

Example 2. Standard show and hide animations

Here is a link to the second sample project. Now we have simple hide and show animations instead of slow fade-in and fast fade-out animations. The main difference that both have default duration, but hide animation has a listener that will be called when the animation is done and will set visibility gone to target view. Here what we have in code:

Example 3. Complex animation.

The last example shows sort of complex animation: we show view and after a while hide it. Here is a link to the project. This example differs from previous one by adding setStartDelay method to fadeOut extension. Here is the code:

Output

You can see all outputs for each example below. There are 6 animations for 2 for each example (expected and actual):

Example 1: Actual(Left) and Expected(Right)
Example 2: Actual(Left) and Expected(Right)
Example 3: Actual(Left) and Expected(Right)

Why?

So why is it happens? Why do we receive incorrect results? The answer lies in the source code of View.java file:

public ViewPropertyAnimator animate() {
if (mAnimator == null) {
mAnimator = new ViewPropertyAnimator(this);
}
return mAnimator;
}

As you see, our view creates only one instance of ViewPropertyAnimator and then use it every time we call view.animate() method. Surely, animator instance doesn’t know when we need to clear all parameters and thus keep it always. That’s why in a first example view only first time fades out fast. Because then animator remembered to use not default duration but custom, defined in fadeIn method. Same thing happens with rest examples.

And there are no alternatives:

  • There is no public constructor of ViewPropertyAnimator to explicitly create a new instance and use it.
  • There is no clear method or something like that to clear all properties and settings of existing ViewProperyAnimator instance
  • There is no newAnimate method in View to create a new ViewPropertyAnimator

Solution

Next solution is not a silver bullet, it is just a workaround to solve described problem. I’ve written a simple util function (for Kotlin it will be extension function):

It will solve our problems, but it has critical drawbacks, that’s why it just a workaround and not a full solution of an entire problem:

  • We have assumed defaults by going to deep details of ViewPropertyAnimator. Moreover, we need to go deeper in ValueAnimator used by first class, to check default values for startDelay, duration and other animation settings
  • We have to maintain that code. When sources of Android SDK will change, we will need to check if defaults for animators were changed and update or application

Afterwards

I hope that Android team will add clear method or some refreshing mechanism to ViewProperyAnimator. Until now you can use a workaround, introduced in this article.

P.S.: Small surprise with Interpolators in ValueAnimator

From documentation of ValueAnimator.setInterpolator method:

The time interpolator used in calculating the elapsed fraction of this animation. The interpolator determines whether the animation runs with linear or non-linear motion, such as acceleration and deceleration. The default value is AccelerateDecelerateInterpolator

So, default value is AccelerateDecelerateInterpolator. For example, I’ve set AccelerateInterpolator, and then you’ve decided to reuse your ValueAnimator for another purpose and now you want to clear interpolator to use default one. But you can not use animator.setInterpolator(null) (we have no clear methods, so cleaning up with passing null is logic for me), because in that case animator will apply LinearInterpolator.

--

--

Michael Spitsin

Love being creative to solve some problems with an simple and elegant ways