Animation Basics for JavaFX Beginners

Build an application step-by-step to see how concurrent and sequential animation of simple objects from point to point works in the JavaFX Script programming language.

How do you create concurrent animation of two or more objects? How do you animate an object so that it begins to move after another object finishes?

The animation of on-screen objects is a manipulation of one or more attributes of the object over time. The following applet shows basic timeline animation of simple shapes. The rectangles show concurrent animation and the circle shows sequential animation. The shapes are simple to reduce the code to as few lines as possible. The focus is on understanding how JavaFX animation works.

Step One: Create One Rectangle

Here's a simple application that does nothing more than create an orange square at a certain location in the window, as shown in Figure 1 and the following JavaFX Script code. Throughout this article, the orange square is called a rectangle, since it is created with an instance of the Rectangle class.

StaticRectangle applicationFigure 1: The StaticRectangle Application

Source Code: Application to Display One Orange Rectangle
package staticrectangle;

import javafx.scene.paint.Color;
import javafx.scene.Scene;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.scene.shape.Line;

Stage {
    title: "One Rectangle"
    //Window size
    width: 240
    height: 200
    scene: Scene {
        //Defines the objects that will be displayed on the screen.
        content: [

            // Draw vertical lines
            for(num in [0..9]) {
                Line {
                    startX: 30+num*20,
                    startY: 60
                    endX: 30+num*20,
                    endY: 80
                    strokeWidth: 1
                    stroke: Color.BLACK
                }
            }

            Rectangle {   // Create a rectangle
                x: 20  //Start drawing 20 pixels from the left of the scene
                y: 60  //Start drawing 60 pixels from the top of the scene
                width: 20,
                height: 20
                fill: Color.web("#FD8500")  //orange
            }
        ]
    }

}

Here are the basic GUI concepts used in this step:

  • The Stage class instance defines the size of the window in pixels and the window title.
  • The Scene class defines the content to be displayed in the window, which in this case is the orange rectangle.
  • The rectangle is declared with a Rectangle object instance of equal height and width.
  • The x and y variables define the horizontal and vertical position of the upper left corner of the rectangle.

For information about JavaFX syntax and GUI application concepts, see the Related Links section at the end of the article.

Step Two: Declare the Animation States

The orange rectangle moves horizontally from its starting point at x:20 to its final location at x:100 over a period of five seconds, as shown in Figure 2.

AnimatedRectangle application Figure 2: The AnimatedRectangle Application

 

In the JavaFX Script programming language, simple point-to-point animation is created with a timeline that contains keyframes defining the points, or animation states. In this example, you need only to define the states at the beginning and end of the animation, plus the type of movement between the two states.

To accomplish this animation, add a Timeline instance that contains two keyframes. One keyframe describes the beginning state, and one keyframe describes the end state, as shown in the following source code. Place the timeline before the Stage instance.

Source Code: Timeline to Animate the Orange Rectangle
var slider1: Number;

Timeline {
    repeatCount: 1
    keyFrames: [
        KeyFrame {  //start point
            time: 0s
            canSkip: true
            values: [
                slider1 => 20.0
            ] 
        } 
        KeyFrame {  //end point
            time: 5s
            canSkip: true
            values: [
                slider1 => 100.0 tween Interpolator.LINEAR
            ] 
        } 
    ] //close keyFrames
}.play();  

Stage {
...
}

Here are the important points about this code:

  • During runtime, the variable slider1 will increase from its value of 20 at zero seconds to a value of 100 at five seconds. The actual movement of the object we see on screen occurs because the value of slider1 is changing.
  • The variable slider1 is an arbitrary name. You can see how it ties into the object being animated in the next section.
  • The movement from the beginning to the end states, commonly called "tweening," is specified with the tween operator. The Interpolator.LINEAR value means that movement will progress at a steady rate from the beginning position to the end position.
  • The Interpolator.LINEAR value means that the value of slider1 changes in a linear fashion from 20 at 0 seconds to 100 at 5 seconds. In other words, Interpolator.LINEAR provides the calculation mechanism for the tween operation to calculate values of slider1 at various intervals. In this case LINEAR determines that the values are calculated at evenly timed intervals.

You've probably noticed that the timeline seems to be missing some information. How does the timeline know which object is being animated? What do the numbers 20 and 100 refer to? This information is provided by the object being animated, as described in the next section.

Step Three: Add Animation Information to the Rectangle

It is the slider1 variable that ties together the information about the animation. It appears in several places in the code, as shown in Figure 3.

How Animation WorksFigure 3: Parts of the Code That Control Animation

The values of slider1 are provided by the KeyFrame instances for the beginning and end of the timeline. In the Rectangle object instance, the x variable must be changed to translateX, and it binds slider1. As the value of slider1 increases in value during the progression of the timeline, the value of translateX will increase, which means that the horizontal position of the rectangle will change. It is the slider1 variable that links the timeline to the correct object. Of course, the slider1 variable can have any name you choose to call it.

Here is the complete code. The timeline is defined as the value of variable t1. This variable will be used to restart the timeline when the Reload button is introduced.

Source Code: Animated Orange Rectangle
package animatedrectangle;

import javafx.animation.Interpolator;
import javafx.animation.Timeline;
import javafx.scene.paint.Color;
import javafx.scene.Scene;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.scene.shape.Line;
import javafx.scene.control.Button;

var slider1: Number = 20;

var t1 = Timeline {
    repeatCount: 1
    keyFrames: [
        at (5s) {slider1 => 100.0 tween Interpolator.LINEAR}
    ] //close keyFrames
    };
t1.play();

Stage {
    title: "Animated Square"
    width: 240
    height: 200
    scene: Scene {
        content: [
        
            // Draw vertical lines
            for(num in [0..9]) {
                Line {
                    startX: 30+num*20,
                    startY: 60
                    endX: 30+num*20,
                    endY: 80
                    strokeWidth: 1
                    stroke: Color.BLACK
                }
            }

            Rectangle {    // Orange rectangle
                translateX: bind slider1  //Horizontal value comes from the keyframes
                y: 60   //Vertical value is constant
                width: 20,
                height: 20
                fill: Color.web("#FD8500")
            }
       ] 
    } 
} 

Timeline Shortcuts

Here are two shortcuts that make coding the timeline a little faster.

Use Default Start Point Instead of KeyFrame Instance

If the start time is zero seconds and you initialize the slider1 variable, you can eliminate the keyframe that defines the start point, as shown in the following source code.

Source Code: Shortcut Eliminating the Start KeyFrame
var slider1: Number = 20;

Timeline {
    repeatCount: 1
    keyFrames: [
        /* end position of orange rectangle and type of movement */
        KeyFrame {
            time: 5s
            canSkip: true
            values: [
                slider1 => 100.0 tween Interpolator.LINEAR
            ] //close values

        } // close KeyFrame

    ] //close keyFrames

}.play();

Use at() Operator Rather Than KeyFrame Instance

Instead of creating KeyFrame instances, you can use the at() operator to specify the beginning and end states, as shown in the following source code.

Source Code: Keyframes Shortcut Using the at() Operator
var slider1: Number;

Timeline {
 repeatCount: 1  
 keyFrames: [
     at (0s) {slider1 => 20.0},  //start point
     at (5s) {slider1 => 100.0 tween Interpolator.LINEAR} //end point
 ]; //close KeyFrames

 }.play();

Combine the Two Shortcuts

If the start time is zero seconds and you initialize the slider1 variable, you can eliminate the at() operator for the start point.

Source Code: Keyframe Shortcut Using the at() Operator and Eliminating the Start Point
var slider1: Number = 20;

Timeline {
  repeatCount: 1  
  keyFrames: [
  at (5s) {slider1 => 100.0 tween Interpolator.LINEAR} //end point only
  ]; 
}.play();

Create Simultaneous Movement

Let's add a black rectangle below the orange one that has the identical horizontal animation of the first rectangle, as shown in Figure 4.

SimultaneousAnimation application Figure 4: The SimultaneousAnimation Application

 

The only change is to add a second rectangle to the scene, as shown in the following source code.

Source Code: Animate Two Rectangles at Different Vertical Positions
package simultaneousanimation;

import javafx.animation.Interpolator;
import javafx.animation.Timeline;
import javafx.scene.paint.Color;
import javafx.scene.Scene;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.scene.shape.Line;
import javafx.scene.control.Button;

var slider1: Number = 20.0;

var t1 =
Timeline {
    repeatCount: 1
    keyFrames: [
            at (5s) {slider1 => 100.0 tween Interpolator.LINEAR} //end point
    ] //close keyFrames

};
t1.play();

Stage {
    title: "Simultaneous Animation"
    width: 240
    height: 200
    scene: Scene {
        content: [
        
            // Draw vertical lines
            for(num in [0..9]) {
                Line {
                    startX: 30+num*20,
                    startY: 60
                    endX: 30+num*20,
                    endY: 80
                    strokeWidth: 1
                    stroke: Color.BLACK
                }
            }

            Rectangle {    // Orange rectangle
                translateX: bind slider1  //Horizontal value from the keyframes
                y: 60   //Vertical value is constant
                width: 20,
                height: 20
                fill: Color.web("#FD8500")
            }
            Rectangle {    // Black rectangle
                translateX: bind slider1  
                y: 90
                width: 20,
                height: 20
                fill: Color.BLACK
            }
         ]
    } 
} 


Because the black rectangle uses the same slider1 variable as the orange rectangle, the animation is identical.

Now suppose you want the rectangles to move simultaneously, but to the left and right of each other on the same vertical line, as shown in Figure 5. The black rectangle will move from x:120 to x:200.

SimultaneousAnimation2 application Figure 5: The SimultaneousAnimation2 Application

 

In this case, you create a new variable to animate the black rectangle and add a new endpoint in the timeline. Then you change the y value of the black rectangle to match the y value of the orange rectangle so that the rectangles have the same vertical position. The changes are shown in the following source code.

Source Code: Animate Two Rectangles at Different Horizontal Positions
var slider1: Number =20;
var slider2: Number =120;

Timeline {
    repeatCount: 1  
    keyFrames: [
 at (5s) {slider1 => 100.0 tween Interpolator.LINEAR}
 at (5s) {slider2 => 200.0 tween Interpolator.LINEAR}
    ] 

}.play();

Stage {
    title: "One Animated Rectangle"
    width: 240
    height: 200
    scene: Scene {
        content: [
        
            // Draw vertical lines
            for(num in [0..9]) {
                Line {
                    startX: 30+num*20,
                    startY: 60
                    endX: 30+num*20,
                    endY: 80
                    strokeWidth: 1
                    stroke: Color.BLACK
                }
            }

            Rectangle {    
                translateX: bind slider1  
                y: 60   
                width: 20,
                height: 20
                fill: Color.web("#FD8500")
            }
            Rectangle {    
                translateX: bind slider2  
                y: 60
                width: 20,
                height: 20
                fill: Color.BLACK
            }
        ] 

    } 

} 

Create Sequential Animation

To complete the application, a circle is added to the shapes on the screen, and it begins moving one second after the rectangles stop. A vertical line is also added to make the animation easier to see. The changes are shown in the following source code.

The following code also includes a Reload button, so that viewers can see the animation repeat without needing to close the application and restart it. All that is required is to use an instance of the Button class and add an action to begin the timeline again when the button is clicked.

Source Code: Animate a Circle After Two Rectangles Finish Moving
package basicanimation;

import javafx.animation.Interpolator;
import javafx.animation.Timeline;
import javafx.scene.paint.Color;
import javafx.scene.Scene;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.scene.shape.Line;
import javafx.scene.control.Button;

var slider1: Number = 20.0;
var slider2: Number = 120.0;
var slider3: Number =110;

var t1 =
Timeline {
    repeatCount: 1
    keyFrames: [
        at (5s) {slider1 => 100.0 tween Interpolator.LINEAR}
        at (5s) {slider2 => 200.0 tween Interpolator.LINEAR}
        at (6s) {slider3 => 110.0 tween Interpolator.LINEAR}  //start point
        at (11s) {slider3 => 10.0 tween Interpolator.LINEAR} //end point
    ] 
};
t1.play();

Stage {
    title: "Horizontal Animation"
    width: 240
    height: 200
    scene: Scene {
        content: [

            // Draw vertical lines
            for(num in [0..9]) {
                Line {
                    startX: 30+num*20,
                    startY: 60
                    endX: 30+num*20,
                    endY: 80
                    strokeWidth: 1
                    stroke: Color.BLACK
                }
            }

            Rectangle {    // Orange rectangle
                translateX: bind slider1  //Value derived from the keyframes
                y: 60   //Vertical value is constant
                width: 20,
                height: 20
                fill: Color.web("#FD8500")
            }
            Rectangle {    // Black rectangle
                translateX: bind slider2
                y: 60
                width: 20,
                height: 20
                fill: Color.BLACK
            }
            Circle {  //Turquoise circle
                translateX: bind slider3
                centerY: 70
                radius: 10
                fill: Color.web("#0A8CEC")
            } 

            /* Reload button 1.2 */
            Button {
                translateX: 80,
                translateY: 135
                text: "Reload"
                action: function() {
                    t1.playFromStart();
                }
            }
        ] 
    }
}

The translateX and y variables of the Rectangle instance specify the coordinates of the upper left corner of the rectangle, whereas the translateX and centerY variables of the Circle instance specify the coordinates of the center of the circle. So, the slider3 and centerY values of the circle were changed to make the circle and rectangle overlap perfectly.

The slider3 variable of the circle is initialized to 110, and the timeline for slider3 starts at 110 at 6 seconds and goes to 10. Because slider3 is bound to translateX, these numbers correspond to the number of pixels from the left side of the window. The circle therefore moves from right to left.

Note that because of the shortcuts, if the slider3 variable is initialized to some value other than 110, it starts at the initialized position at 0 seconds and moves to 110 for the first 6 seconds. For example, if slider3 is initialized to 20, it moves from position x:20 to x:110 for the first 6 seconds, then moves from x:110 to x:10 between 6 and 11 seconds.

JavaFX Mobile Emulator Figure 6: JavaFX Mobile Emulator

Try It

Here are some simple changes you can make to the code to test your understanding.
  • If you have a Windows installation, try running the application in the JavaFX Mobile Emulator. See Deploy A Rich Internet Application Developed With JavaFX Technology for instructions.
  • Add some vertical animation by using the translateY variable. Trying moving a shape from bottom to top as well as top to bottom. Note: If you assign a constant value to translateX or translateY, it behaves the same as x and y in a rectangle or centerX and centerY in a circle.
  • Create simultaneous horizontal and vertical animation. For help, see Lesson 7: Creating Animated Objects in the tutorial on building a GUI application.
  • Try some other types of movement with keyframe animation. For example, the Interpolator class has a number of variables besides LINEAR. Substitute some of the other variables and see what happens to the animation.
  • Change the overlap so that the rectangle is on top of the circle. Hint: The order in which the shapes are declared in the scene determines their ordering from front to back.
  • Instead of moving the rectangles and circles, try enlarging them dynamically. Hint: The variable that you use in the KeyFrame instance in the timeline must be bound to the width variable for the Rectangle objects and the radius variable for the Circle object.

Related Links