Bounce: Nested Timeline Animation
The JavaFX Timeline class enables you to animate variables, and therefore objects, over time. Though you might often animate only one object at a time, the Timeline actually supports nested timelines, which enable you to build more complex animations. This example shows a master timeline that contains three nested timelines:
- Timeline one moves the ball back and forth.
- Timeline two moves the ball up and down.
- Timeline three "squishes" the ball when it contacts the floor.
Understanding the Code
The model for this sample is pretty simple, as Figure 1 shows. The ball is defined by four variables: x, y, sx, and sy. The x and y variables represent the ball's current location. The sx and sy variables are the scale x and scale y of the ball. These two variables give the ball the appearance of squishing when it contacts the base of the floor.
//width and height of the screen var w = 240; var h = 320; // model variables def rad = 50; // radius of the ball var x = 0.0; // x location of the ball var y = 0.0; // y location of the ball var sx = 1.0; // scale x var sy = 1.0; // scale y var color1 = Color.SILVER; var color2 = Color.MAROON;
Figure 1: Ball Data Model
There individual timelines animate the ball in different directions, as Figure 2 shows. The ax timeline animates the ball from side to side over 10 seconds. The ay timeline moves the ball up and down every 4.5 seconds. It uses a SPLINE interpolator to create a smoother bouncing motion. A third timeline, sxy, performs the actual squishing when the ball nears the floor.
// animate side to side
var ax = Timeline {
keyFrames:[
KeyFrame { time: 0s values: x => rad },
KeyFrame { time: 3s values: x => w-rad tween Interpolator.LINEAR },
]
autoReverse: true
repeatCount: Timeline.INDEFINITE
}
// animate up and down
var ay = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: [
KeyFrame { time: 0s values: y => 0.0 },
KeyFrame { time: 2.2s values: y => h-rad tween Interpolator.SPLINE(0,0,.5,0) },
KeyFrame { time: 2.25s values: y => h-rad },
KeyFrame { time: 4.5s values: y => 0.0 tween Interpolator.SPLINE(0,0,0,0.5) },
]
}
// animate the squish when boucing
var sxy = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: [
KeyFrame { time: 2s values: [sx => 1.0, sy => 1.0 ] },
KeyFrame { time: 2.25s
values: [
sx => 1.2 tween Interpolator.LINEAR,
sy => 0.7 tween Interpolator.LINEAR,
]
},
KeyFrame { time: 2.5s
values: [
sx => 1.0 tween Interpolator.LINEAR,
sy => 1.0 tween Interpolator.LINEAR,
]
},
KeyFrame { time: 4.5s
values: [
sx => 1.0 tween Interpolator.LINEAR,
sy => 1.0 tween Interpolator.LINEAR,
]
},
]
}
Figure 2: Ball Movement Timelines
The three timelines are nested together in a master timeline called clip, which loops forever, as Figure 3 shows.
// loop the other timelines forever
var clip = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames:
KeyFrame {
time: 0s
timelines: [ax, ay, sxy]
}
}
Figure 3: Master Timeline
Now that the data model is animated properly, you can bind a shaded circle to the model to create a ball, as shown in Figure 4. The ball itself is a Circle with a radius of 50 and a fill that uses a RadialGradient to create the shading effect. To move the ball, the circle's transforms variable is bound to a sequence of four transforms. The first transform moves the ball. The second transform translates to the center of the ball so that the third transform performs do the squishing properly. The fourth transform simply undoes the second one.
Circle {
centerX: 0 centerY: 0 radius: rad
stroke: Color.BLACK
fill: RadialGradient {
proportional: false radius: 70
centerX: 0 centerY: 0
focusX: 20 focusY: -50
stops: [
Stop { offset: 0.0 color: color1 },
Stop { offset: 0.85 color: color2 },
Stop { offset: 1.0 color: color2 }
]
}
transforms: bind [
Transform.translate(x, y), // move left and right and up and down
Transform.translate(50.0, 50.0), // translate to the center of the ball
Transform.scale(sx, sy), // do the squish
Transform.translate(-50.0, -50.0) // un-translate
]
}
Figure 4: Displaying the Actual Ball
Josh Marinacci

