Spring Animation Effects With a Custom Interpolator

By Josh Marinacci, October 19th, 2008

JavaFX provides default interpolators for tween animation, but you can also create your own for some cool effects. This example shows how to create a spring interpolator.

Understanding the Code

By default an animation is linear, meaning the object being animated moves at a constant speed. An "ease-in" animation means the objects starts moving slowly and then speeds up. "Ease-out" is the reverse, with an object starting at normal speed then slowing down when it nears the end. This behavior is controlled by an Interpolator object. JavaFX has default interpolators for LINEAR, EASEIN, EASEOUT, and ease-in plus ease-out (EASEBOTH). However, sometimes you want a different kind of interpolation. JavaFX enables you to create your own custom interpolators by subclassing Interpolator or SimpleInterpolator. This example shows you how to create a springlike motion by using a custom interpolator.

The interpolator itself subclasses SimpleInterpolator and implements a single function: curve. This is the curve of t (time) over the length of the animation. By returning different values of t as it goes from 0.0 to 1.0 (representing the duration of the animation) you can create complex interpolation behavior. To create a springlike animation the code in Figure 1 creates the SpringInterpolator using the standard physics spring equation.

Source Code
import javafx.animation.SimpleInterpolator;
import java.lang.Math;

public class SpringInterpolator extends SimpleInterpolator {
    // the amplitude of the wave
    // controls how far out the object can go from it's final stopping point.
    public-init var amplitude:Number = 1.0;
    // determines the weight of the object
    // makes the wave motion go longer and farther
    public-init var mass:Number = 0.058;
    // the stiffness of the wave motion / spring
    // makes the motion shorter and more snappy
    public-init var stiffness:Number = 12.0;
    // makes the wave motion be out of phase, so that the object
    // doesn't end up on the final resting spot.
    // this variable is usually never changed
    public-init var phase:Number = 0.0;

    // if this should do a normal spring or a bounce motion
    public-init var bounce:Boolean = false;


    // internal variables used for calcuations
    var pulsation:Number;

    init {
        this.pulsation = Math.sqrt(stiffness / mass);
    }


    // the actual spring equation
    override public function curve(t: Number) : Number {
        var t2 = -Math.cos(pulsation*t+phase+Math.PI) * (1-t) * amplitude ;
        // use the absolute value of the distance if doing a bounces
        if(bounce) {
            return 1-Math.abs(t2);
        } else {
            return 1-t2;
        }
    }
}

Figure 1: SpringInterpolator.fx Code

To use a custom interpolator, you must create an instance of it, then use it in your keyframes with the tween keyword. The code in Figure 2 creates a new SpringInterpolator then defines a variable, springAnimY. The springAnim timeline animates the springAnimY variable from 0 to 200 over 1.5 seconds. The tween spring makes the animation use the spring interpolator instead of a standard interpolator. Finally the group containing a ball image is bound to the springAnimY variable, making it move with the timeline.

Source Code
def spring = SpringInterpolator { bounce: false};
var springAnimY = 50;
var springAnim = Timeline {
    keyFrames: [ at(1.5s) { springAnimY => 200 tween spring}, ]
};
// bind the to the springAnimY variable which will animate it
var springImage = ImageView {
    translateX: 20
    translateY: bind springAnimY;
    image: Image { url: "{__DIR__}image/ball.png" }
    onMousePressed:function(e:MouseEvent) {
        springAnim.time = 0s;
        springAnim.play();
    }
};

Figure 2: SpringTest.fx Code

This example actually has two animations, one for spring and one for bouncing. The bouncing version is exactly the same equation except that it uses the absolute value of t to reflect the spring waves. (See the end of Figure 1) As a result the object appears to bounce off of its endpoint rather than going past it and coming back. To use this behavior, set the bounce variable of the SpringInterpolator to true.