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:Number = rad; // x location of the ball var y:Number = rad; // y location of the ball var sx = 1.0; // scale x var sy = 1.0; // scale y var color1 = Color.web("#098cec"); var color2 = Color.web("#7dd6ff"); var color2b = Color.web("#d2eddf");
Figure 1: Ball Data Model
Three 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 = Timeline {
keyFrames:[
KeyFrame { time: 0s values: x => rad },
KeyFrame { time: 3s values: x => scene.width - rad tween Interpolator.LINEAR },
]
autoReverse: true
repeatCount: Timeline.INDEFINITE
}
// animate up and down
var ay:Timeline = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: [
KeyFrame { time: 0s values: y => rad },
KeyFrame { time: 2.1s values: y => scene.height - rad tween Interpolator.SPLINE(0,0,.5,0) },
KeyFrame { time: 2.4s values: y => scene.height - rad tween Interpolator.LINEAR},
KeyFrame { time: 4.5s values: y => rad tween Interpolator.SPLINE(0,0,0,0.5) },
]
}
// animate the squish when boucing
var sxy = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: [
KeyFrame { time: 2.1s 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.4s
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 ParallelTransition called clip, which plays all the animations in parallel, as Figure 3 shows.
clip = ParallelTransition {
node: ball
content: [ax, ay, sxy, textop]
}
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 two transforms. The first transform moves the ball. The second transform scales the ball to illustrate squishing.
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.60 color: color2 },
Stop { offset: 1.0 color: color2b }
]
}
transforms: bind [
Transform.translate(x, y), // move left and right and up and down
Transform.scale(sx, sy, rad, rad), // do the squish
]
}
Figure 4: Displaying the Actual Ball
Josh MarinacciStaff Engineer,
Sun Microsystems
