Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

JavaWorld Daily Brew

Key-Frame Animation

 

It's unfortunate that OpenJFX currently isn't a real open-source project. As such, it gives the appearance that progress isn't being made with JavaFX Script. Nevertheless, evolution has occurred, albeit internally.

I've worked on replacing the animation framework in JavaFX with something more complete and consistent. This was always intended to happen. The presently available animation mechanism (the "dur" operator) was a temporary solution added to F3 more than a year ago, was never thought to be an adequate solution, and clearly isn't to anyone who has ever actually tried to use it.

The supported animation idiom is now that of "Key Frame" animation, as in traditional animation and visual design tools. My view (after significant research) is that this same idiom is also the most effective technique for describing animations programmatically. The concept is that you describe the animated state transitions of your "scene" by declaring start and end "snapshots" of state at certain points in time, and declare interpolation functions to calculate the in-between states.

Given this input the system can automatically perform the animation, stop it, pause it, resume it, reverse it, or repeat it when requested. Thus it is a "declarative" animation technique.

Programmatically such "snapshots" consist of specifying the values for JavaFX variables - local variables and instance variables.

Here are some examples:

  1. Simple A very simple example, which demonstrates simultaneous animation of the rotation, scale, and fill color of a rectangle.
  2. Ball Consists of three concurrent repeating animations: one for the x coordinate of the ball, one which animates the y coordinate, and the one which animates the scale to create a "bounce" effect. See line 65 for the animations.
  3. Motomaxxve Consists of only one keyframe animation whose "position" is bound to a Slider, such that you can manually "play" it by moving the slider. Left-click on the content will play it forward, Right-click will reverse it. One interesting point is that the text animations are dynamically generated.
  4. EG Consists of several animations (fade, slide): one of which is a kind of ticker-tape view of the presenters at the 2007 "Entertainment Gathering" at the Getty Museum. The others support a slide show view.
  5. Poker Video poker game. Consists of two animations: 1 for the deal, and 1 for the draw, each of which animates the bet, turning over the cards, and scoring (if you have a winning hand).

You'll want to take a look at the examples for reference as you read the below, I think.

Time Literals

Since time is fundamental to animation, I've added time "literals" to the language. These are instances of the following primitive class:

public class Time extends java.lang.Comparable {

    public attribute millis: Integer;
    public attribute seconds: Number;
    public attribute minutes: Number;
    public attribute hours: Number;

    public operation toMillis():Integer;
    public operation add(other:Time):Time;
    public operation subtract(other:Time):Time;
    public operation multiplyBy(n:Number):Time;
    public operation divideBy(n:Number):Time;
    public operation equals(other): Boolean;
}

Numeric Literals with one of the suffixes (h, m, s, ms) are interpreted as Time literals, e.g:

    2s == Time {seconds: 2};  // true
Relational operators and arithmetic operators other than % are overloaded for Time types:

    2.5s < 5000ms;   // true

    2.5s * 3  == 7.5s; // true

Timelines

A KeyFrame describes a set of end states of various properties of objects at a certain time instant relative to the start of the animation, together with interpolation specifications to calculate the in-between values relative to the previous frame. Lack of an interpolation specification indicates a "discrete" animation, meaning the value of the property instantaneously transitions to the value given in the key frame at the time instant of the key frame. A Timeline is an object that contains a list of key frames, together with operations to control the overall animation, to start it, stop it. repeat it, etc.

Timelines are instances of the following class:

public class Timeline {
    public attribute keyFrames: KeyFrame*;
    public attribute repeatCount: Number;
    public attribute autoReverse: Boolean;
    public operation start();
    public operation stop();
    public operation pause();
    public operation resume();
    public operation reverseNow();
    public attribute position: Number?; // unit time interval	
}

where KeyFrame is:

public class KeyFrame {
    public attribute time: Time;
    public attribute keyValues: InterpolatedLValue*;
    public attribute action: function()?;
    public attribute timelines: TimeLine*; // included timelines
}

InterpolatedLValue has the following definition:

public class InterpolatedValue {
    public attribute value: Object;
    public attribute interpolate: function(values:Object*, unitTimeInterval:Number):Object?;
}

public class InterpolatedLValue extends InterpolatedValue {
    public attribute target: &Object;
}

InterpolatedValue describes a pair (<value>, <interpolation_function>).

InterpolatedLValue describes a triple (<target property>, <value>, <interpolation function>).

Properties

Specification of the target property required adding "pointers" to the JavaFX script language. Pointer types are declared with the "&" symbol (as in the target attribute above), and pointer instances are obtained with the unary "&" operator and dereferenced with the unary "*" operator, as in C, e.g:

    var x = 2;
    var px = &x;
    *px == 2; // true
    *px = 3;
    x == 3; // true
Properties in JavaFX are pointers to local variables or object attributes. In cases where these are "sequences" a pointer to an element of such a sequence is also a valid property.

Syntax

Although KeyFrame animations are normal JavaFX objects, special syntax is provided to make it easier to express than is possible with the standard object-literal syntax.

The "tween" operator is a a literal constructor for InterpolatedValue.

    100 tween LINEAR;
is equivalent to
    InterpolatedValue { value: 100, interpolate: LINEAR:Number }
The "=>" operator provides a literal constructor for a list of InterpolatedLValues:
    var x = 2;

    x => 100 tween LINEAR;
is equivalent to the following
    var x = 2;

    InterpolatedLValue {target: &x, value: 100, interpolate: LINEAR:Number};
However, you can also apply "=>" to a whole set of object properties using an object-literal like notation rather than just single property or variable, for example like this:
     var rect = Rect {};

     rect => {height: 400 tween EASEBOTH, width: 500, fill: blue tween LINEAR, clip: { shape: Rect => {height: 500, width: 600} };
The second line above is equivalent to this:
[InterpolatedLValue {
     target: &rect.height
     value: 500
     interpolate: EASEBOTH:Number
},
InterpolatedLValue {
     target: &rect.width
     value: 500
      interpolate: null
},
InterpolatedLValue {
      target &rect.fill
      value: blue:Color
      interpolate: LINEAR:Color
},
InterpolatedLValue {
      target: &((Rect)rect.clip.shape).height
       value: 500
       interpolate: null
},
InterpolatedLValue {
      target: &((Rect)rect.clip.shape).width
      value: 600
      interpolate: null
}]
Finally, the "at" and "after" operators are literal constructors of KeyFrame objects:
    var x = 2;
    var rect = Rect {...};

    at (2s) {
        x => 2 tween LINEAR;
        rect => {width: 400 tween EASEBOTH, fill: red tween EASEBOTH};
        trigger {
           println("at 2 seconds...");
        }
    }

    after (5s) {
        x => 100 tween EASEBOTH;
    }
The "trigger" clause allows you to associate an arbitrary callback with the key frame.

The time specified by "at" is relative to the start of the Timeline. The time specified by "after" is relative to the previous key frame.

The first example above is equivalent to:

    KeyFrame {
        time: 2s
        action: operation() { println("at 2 seconds..."); }
        keyValues:
        [InterpolatedLValue {
           target &x
           value: 2
           interpolate: LINEAR:Number
        },
        InterpolatedLValue {
           target: &rect.width
           value: 400
           interpolate: EASEBOTH:Number
        },
        InterpolatedLValue {
           target: &rect.fill
           value: red:Color
           interpolate: EASEBOTH:Color
        }]
    }
Timelines and KeyFrames may be composed hierarchically - a KeyFrame may use the "include" operator to include any number of Timelines, in which case the key frames that make up the included timelines are merged into the containing timeline at the time instant of the key frame. The above "Ball" example demonstrates this, reproduced here:
    var ax = Timeline {
        // x
        keyFrames:
        [at (0s) {
            x => 0;
        },
        at (10s) {
            x => 700 tween LINEAR;
        }]
        autoReverse: true
        repeatCount: INFINITY
    }
    
    var ay = Timeline {
        // y
        repeatCount: INFINITY
        keyFrames:
        [at (0s) {
            y => 0;
        },
        at (2.2s) {
            y => 375 tween SPLINE(0, 0, .5, 0);
        },
        at (2.25s) {
            y => 375;
        },
        at (4.5s) {
            y => 0 tween SPLINE(0, 0, 0, 0.5);
        }]
    }
    
    var sxy =  Timeline {
        // scale x y
        repeatCount: INFINITY
        
        keyFrames:
        [at (2.15s) {
            sx => 1;
            sy => 1;
        },
        at (2.25s) {
            sx => 1.2 tween LINEAR;
            sy => .7 tween LINEAR;
        },
        at (2.5s) {
            sy => 1 tween LINEAR;
            sx => 1 tween LINEAR;
        },
        at (4.5s) {
            sx => 1;
            sy => 1;
        }]
    }
    
    var clip = Timeline {
        repeatCount: INFINITY
        keyFrames: 
        at (0s) { 
           include ax, ay, sxy;
        }
    }
In the above example, the "clip" timeline combines the other three animations. Playing "clip" will play all the animations simultaneously (yet still taking into account each ones individual repeat behavior).