Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animate an animation cancel

Tags:

android

Say I'm animating a button from x=0 to x=200, using:

ObjectAnimator animator = ObjectAnimator.ofFloat(button, "x", 0f, 200f);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.start();

Now, when the button is at x=100, I want to cancel the animation. When I call animator.cancel(), the button stops abruptly. What I'm trying to achieve is that the x value gradually slows down (somewhat like a DecelerateInterpolator), and neatly comes to a stop at, say, x=120.

Ofcourse, I could just cancel() the animation and start a new decelerating animator. However, this does not take the current velocity of the button in account, leading to weird behavior.

How would I do this?

like image 729
nhaarman Avatar asked Jan 14 '14 17:01

nhaarman


2 Answers

As correctly pointed out by @Gil, You have to deal with your custom Interpolator implementation. The good news is that you don't actually need to implement everything yourself. You can just combine 2 different interpolation formulas: accelerate/decelerate for the main animation and decelerate interpolator for cancellation.

Essentially that's what you are looking for:

Normal accelerate/decelerate animation: enter image description here

Your animation when you cancel it somewhere in the middle: enter image description here

Here is my quick interpolator implementation:

static class MyInterpolator extends AccelerateDecelerateInterpolator {

    private float phaseShift = 0f;
    private boolean isCancelled = false;
    private float lastInput = 0f;

    /**
     * Maps a value representing the elapsed fraction of an animation to a value that represents
     * the interpolated fraction. This interpolated value is then multiplied by the change in
     * value of an animation to derive the animated value at the current elapsed animation time.
     *
     * @param input A value between 0 and 1.0 indicating our current point
     *              in the animation where 0 represents the start and 1.0 represents
     *              the end
     * @return The interpolation value. This value can be more than 1.0 for
     * interpolators which overshoot their targets, or less than 0 for
     * interpolators that undershoot their targets.
     */
    @Override
    public float getInterpolation(float input) {
        lastInput = input;
        if(!isCancelled)
        {
            return super.getInterpolation(input);
        }
        else
        {
            return getCancellationInterpolation(input) - phaseShift;
        }
    }

    public void cancel()
    {
        isCancelled = true;
        this.phaseShift = getCancellationInterpolation(lastInput) - super.getInterpolation(lastInput);

    }

    private float getCancellationInterpolation(float input)
    {
        return (1.0f - (1.0f - input) * (1.0f - input));
    }
}

As you can see, I use default interpolation for normal animation and switch do deceleration one when animation is canceled. Obviously this code is not perfect (it doesn't reset phaseShift value and isCancelled flag which causes miscalculation if you use repeat mode), but that's something you hopefully can figure out yourself :)

I created sample project on GitHub, so you can see how it looks like

FOLLOW UP I played a bit more with formulas and taken the second part of DecelerateInterpolator implementation. I introduced factor parameter which helps you to control how fast your cancellation should happen (some sort of traction). Setting factor to 1.5 gives me this: enter image description here

As you can see, when I hit cancel at ~0.5 point, animation gets cancelled more quickly (so it doesn't go all the way to the 0.7 of the distance as in previous example). It gives a bit better feeling of real object. Higher factor - faster your animation will stop.

Here is an updated interpolator:

static class MyInterpolator extends AccelerateDecelerateInterpolator {

    ......
    private float factor = 1.5f;
    .......

    private float getCancellationInterpolation(float input)
    {
        if(factor == 1)
        {
            return (1.0f - (1.0f - input) * (1.0f - input));
        }
        else
        {
            return (float)(1.0f - Math.pow((1.0f - input), 2 * factor));
        }
    }
}
like image 180
Pavel Dudka Avatar answered Oct 17 '22 01:10

Pavel Dudka


You run your animation all the way through and you implement your TimeInterpolator that "slows down" after accelerating.

You need to implement the method getInterpolation(float): this represents a mathematical function that maps time instants to your position values x normalised between 0 and 1. For instance, if you wanna run from x = 0 to x = 120, the value x = 100 corresponds to the normalised value

100/|120 - 0| = 0.83

Finding the right function requires some mathematical sophistication and some guessing, but this tutorial should get you started.

like image 41
Gil Vegliach Avatar answered Oct 16 '22 23:10

Gil Vegliach