Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
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:
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).