Mittwoch, 21. August 2013

Beef up your animations with easing functions

If you have ever worked with JavaScript frameworks like jQuery or MooTools you probably also stumbled over the pletora of easing functions which are used for animation.

If you still don't know what I am talking about..., I am talking about the popular easing functions developed by Robert Penner, which have since been implemented in many languages and frameworks.

Time to have these fantastic functions around in JavaFX!

One thing, all easing functions have in common is the "mode". They either ease in, ease out or do both.

If you just take the result of a function f(x) directly, it is easing in.

x is the normalized time between 0 and 1 and the function should return 0 for x = 0 and 1 for x = 1, so that your animation starts and ends at the desired value.

If you want to reverse this function into the "easeOut" mode, the formular for this is simply:

1 - f(1-x)

And the third option is to have the function at the beginning and the end of the animation, which is usually referred to as "easeInOut".

The formular for this is slightly more complex:
if (x <= 0.5) {
   return f(2 * x) / 2;
} else {
   return (2 - f(2 * (1 - x))) / 2;
}

JavaFX already gives you Interpolator.EASE_IN, Interpolator.EASE_OUT and Interpolator.EASE_BOTH, which give you an easing behavior similar to a quadratic function.

But if you want to write your own easing functions, you basically have to extend javafx.animation.Interpolator.

What we should do first, is to write a base interpolator which does all the math for the three different easing modes and later let all other functions derive from that interpolator.

Here's the abstract base class I wrote, which does the basic easing stuff:

public abstract class EasingInterpolator extends Interpolator {

    /**
     * The easing mode.
     */
    private ObjectProperty<EasingMode> easingMode = new SimpleObjectProperty<>(EasingMode.EASE_OUT);

    /**
     * Constructs the interpolator with a specific easing mode.
     *
     * @param easingMode The easing mode.
     */
    public EasingInterpolator(EasingMode easingMode) {
        this.easingMode.set(easingMode);
    }

    /**
     * The easing mode property.
     *
     * @return The property.
     * @see #getEasingMode()
     * @see #setEasingMode(EasingMode)
     */
    public ObjectProperty<EasingMode> easingModeProperty() {
        return easingMode;
    }

    /**
     * Gets the easing mode.
     *
     * @return The easing mode.
     * @see #easingModeProperty()
     */
    public EasingMode getEasingMode() {
        return easingMode.get();
    }

    /**
     * Sets the easing mode.
     *
     * @param easingMode The easing mode.
     * @see #easingModeProperty()
     */
    public void setEasingMode(EasingMode easingMode) {
        this.easingMode.set(easingMode);
    }

    /**
     * Defines the base curve for the interpolator.
     * The base curve is then transformed into an easing-in, easing-out easing-both curve.
     *
     * @param v The normalized value/time/progress of the interpolation (between 0 and 1).
     * @return The resulting value of the function, should return a value between 0 and 1.
     * @see Interpolator#curve(double)
     */
    protected abstract double baseCurve(final double v);

    /**
     * Curves the function depending on the easing mode.
     *
     * @param v The normalized value (between 0 and 1).
     * @return The resulting value of the function.
     */
    @Override
    protected final double curve(final double v) {
        switch (easingMode.get()) {
            case EASE_IN:
                return baseCurve(v);
            case EASE_OUT:
                return 1 - baseCurve(1 - v);
            case EASE_BOTH:
                if (v <= 0.5) {
                    return baseCurve(2 * v) / 2;
                } else {
                    return (2 - baseCurve(2 * (1 - v))) / 2;
                }

        }
        return baseCurve(v);
    }
}

Then let the actual implementation for each easing function derive from that base class.

For simplicity, here's only the quadratic interpolator. All other implementations, including the more complex ones, like elastic, bounce and back interpolators can be found in my repository.

public class QuadraticInterpolator extends EasingInterpolator {

    /**
     * Default constructor. Initializes the interpolator with ease out mode.
     */
    public QuadraticInterpolator() {
        this(EasingMode.EASE_OUT);
    }

    /**
     * Constructs the interpolator with a specific easing mode.
     *
     * @param easingMode The easing mode.
     */
    public QuadraticInterpolator(EasingMode easingMode) {
        super(easingMode);
    }

    @Override
    protected double baseCurve(double v) {
        return Math.pow(v, 2);
    }
}

Have fun using them ;-).

There's also a sample jar file for download, which demonstrates each interpolator.