Building GUI Applications With JavaFX

Lesson 7: Creating Animated Objects

By Alla Redko
Version: JavaFX 1.2 « Previous 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Next »
 
The JavaFX libraries provide built-in support to create animation.  This lesson shows you how to build a graphical object and then animate it using linear interpolation, a type of key frame animation supported by JavaFX libraries. The example introduced in this lesson uses the declarative syntax of the JavaFX Script Language, as well as data binding, graphics, and node-specific features, so it may be helpful for you to become familiar with Learning the JavaFX Script Programming Language, Presenting UI Objects in a Graphical Scene, and Creating Graphical Objects.
 

This lesson is divided into two sections. The common profile includes actions for using animation both for mobile and desktop applications. The second section adds some visual effects to run the application in the desktop window.

Common Profile

 
Creating an Application Scene
Adding a Background Image
Drawing a Cloud
Creating Horizontal Motion
Controlling the Timeline Cycle
Adding Vertical Motion
 

This section introduces a step-by-step procedure that demonstrates how to add animation to a simple application. It will guide you through the creation of a cloud that travels across a sky of sunshine, and bounces off the window borders, as displayed in the following window.

Creating an Application Scene

As you learned in Presenting UI Objects in a Graphical Scene, UI components, shapes, text, and images are considered a hierarchy of objects in a graphical scene. Animation of these graphical objects also takes place in scene, so the first step is to create a scene.

  1. Add import statements for the Stage, Scene, and Color classes.

  2. Add the Stage object literal and define the title instance variable.

  3. Add the Scene object literal to the scene instance variable of the Stage class.

  4. Define the color of the scene using the fill variable of the Scene class.

    import javafx.stage.Stage;
    import javafx.scene.Scene;
    import javafx.scene.paint.Color;
    
    Stage{
      title: "Cloud"
      scene:
         Scene{
           fill: Color.WHITE
         }//Scene
    }//Stage
    
     

Refer to Using Declarative Syntax for more information about declarative syntax employed in the JavaFX Script programming language.

Adding a Background Image

In the JavaFX SDK, images are created using the javafx.scene.image.Image class, where the image location is specified in the url instance variable. Note that only a Node object can be added to a scene's content, so you need to use an adapter class, called ImageView. More details about scene and nodes are in Presenting UI Objects in a Graphical Scene.

  1. Add two new imports for the Image and ImageView classes.

  2. Set an image that will serve as a background for the traveling cloud. Use the sun.jpg image. When specifying the image url, you can set the URI of the resource or use the relative codebase. In this example the image url is set using the __DIR__ variable that indicates the directory where the image is located. By default it points to the current directory, so make sure that the sun image is located in the same directory as application compiled classes. To run the application on the mobile emulator make sure that the image is packed into the application jar file along with the compiled classes.

These changes are reflected in the modified code shown below:

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
Stage{
  title: "Cloud"
  scene:
     Scene{
       fill: Color.WHITE
       content:[
        ImageView{
		    image: Image{
			       url:  "{__DIR__}sun.jpg"
		     }//Image
        }//ImageView
       ]
     }//Scene
}//Stage
 

When compiled and run on the mobile emulator, this modified code produces the following window.

A frame with an image as background
Figure 1: A scene with an image as background
 
Drawing a Cloud

Create the actual cloud by drawing five successive arcs, joining the last one to the first. The end point of the first arc is the start point of the second arc. This is illustrated in the following diagram.

End-points of arcs that form cloud

Figure 2: End-points of arcs that form cloud
 

To draw this cloud in your code you need to perform the following steps:

  1. Add import statements for the MoveTo, ArcTo, Path, LinearGradient, and Stop classes. Refer to Creating Graphical Objects for more information about shapes and filling methods.

  2. Use the MoveTo, ArcTo, and Path classes from the javafx.scene.shape package as shown in the following code fragment.

       Path {
           stroke: Color.DODGERBLUE
           fill: LinearGradient {  
               startX:60, 
               startY:10, 
               endX:10 
               endY:80 , 
               proportional: false   
               stops: [  
                   Stop {offset: 0.0 color: Color.DODGERBLUE},  
                   Stop {offset: 0.5 color: Color.LIGHTSKYBLUE},
                   Stop {offset: 1.0 color: Color.WHITE}
               ]
           }             
           elements: [
               MoveTo {x: 15 y: 15 },
               ArcTo {x: 50 y: 10 radiusX: 20 radiusY: 20 sweepFlag: true},
               ArcTo {x: 70 y: 20 radiusX: 20 radiusY: 20 sweepFlag: true},
               ArcTo {x: 50 y: 60 radiusX: 20 radiusY: 20 sweepFlag: true},
               ArcTo {x: 20 y: 50 radiusX: 10 radiusY: 5 sweepFlag: true},
               ArcTo {x: 15 y: 15 radiusX: 10 radiusY: 10 sweepFlag: true},
            ]   
        }//Path
    
     
    The MoveTo class indicates the start point for the shape, and the ArcTo class creates an arc segment. All segments are combined into a shape using the Path class which represents a simple shape, and enables basic construction of a geometric path. The Path class is helpful when you need to create your own shape that is different from the primitive graphic shapes available in the javafx.scene.shape package. The Path class extends the Node class and inherits all of its instance variables and functions.


    Note: The sweepFlag instance variable is set to true so that the arc be drawn clockwise, in a "positive" angle. If the arcs are drawn counterclockwise, they will not curve correctly.

The following modified code includes a graphical scene, an image, and a cloud:

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.ArcTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;

Stage{
    title: "Cloud"
    scene: Scene{
            fill: Color.WHITE
            content:[
                ImageView{
                    image: Image{
                        url: "{__DIR__}sun.jpg"
                    }
		},//ImageView
                Path {
                    stroke: Color.DODGERBLUE
                    fill: LinearGradient {  
                        startX:60, 
                        startY:10, 
                        endX:10 
                        endY:80 , 
                        proportional: false   
                        stops: [  
                            Stop {offset: 0.0 color: Color.DODGERBLUE},  
                            Stop {offset: 0.5 color: Color.LIGHTSKYBLUE},
                            Stop {offset: 1.0 color: Color.WHITE}
                        ]
                    }//LinearGradient             
                    elements: [
                        MoveTo {x: 15 y: 15 },
                        ArcTo {x: 50 y: 10 radiusX: 20 radiusY: 20 sweepFlag: true},
                        ArcTo {x: 70 y: 20 radiusX: 20 radiusY: 20 sweepFlag: true},
                        ArcTo {x: 50 y: 60 radiusX: 20 radiusY: 20 sweepFlag: true},
                        ArcTo {x: 20 y: 50 radiusX: 10 radiusY: 5 sweepFlag: true},
                        ArcTo {x: 15 y: 15 radiusX: 10 radiusY: 10 sweepFlag: true},
                    ]   
                }//Path
            ]
        }//Scene
}//Stage
 

When compiled and run in a desktop window, this modified code produces the following window.

A frame with an image and a cloud-looking shape

Figure 3: A window with an image and a cloud-looking shape
 
Creating Horizontal Motion

The next step is to animate the cloud. The JavaFX Script Language supports the Key Frame animation concept. This means that the animated state transitions of the graphical scene are declared by start and end snapshots (key frames) of the scene's state at certain points in time. Given these two states, the system can automatically perform the animation. It can stop, pause, resume, reverse or repeat movement when requested.

First, simplify the task by animating the cloud horizontally, with no vertical motion.  Later you will add in the vertical motion. To animate the cloud horizontally, alter the translateX instance variable of the Path object, and leave the translateY instance variable constant. Perform the following steps:

  1. Set the translateY variable of the Path object to 100.

  2. Define key frames for the start point (0, 100) and the end point (158, 100). To calculate these values, take into consideration the image size, which is 241x332, and the shape size, which is 83x64. These measurements are illustrated in the following diagram.

    Key Frames

    Figure 4: Key Frames
     
    Animation occurs along a timeline, represented by a javafx.animation.Timeline object. Each timeline contains two or more key frames, represented by javafx.animation.KeyFrame objects.

  3. Create a timeline with two key frames to perform the cloud's horizontal movement and starts the movement when the application is launched. Positions between the start and the end points are calculated using linear interpolation.

    import javafx.animation.Timeline;
    import javafx.animation.KeyFrame;
    import javafx.animation.Interpolator;
    
    var x: Number;
    
    Timeline {
      keyFrames: [
             KeyFrame{
                  time: 0s
                  values: x => 0.0
             },
    
             KeyFrame{
                  time: 4s
                  values: x => 158.0 tween Interpolator.LINEAR
             } 
      ]
    }.play();
    
     
    The time instance variable defines the elapsed time at which the associated values will be set within a single cycle of the Timeline object. The time is a variable of the javafx.lang.Duration class that takes a Number value followed by "s" or "ms" to indicate seconds or milliseconds. The => operator provides a literal constructor for a list of key values. The tween operator is a literal constructor for an interpolated value. Therefore the cloud begins at pixel 0 and moves to position 158 over the course of four seconds.

    Although KeyFrame animations are typical JavaFX objects, special syntax is provided to make it easier to express animation than is possible with the standard object-literal syntax. The trigger clause enables you to associate an arbitrary callback with the key frame. The time specified by at is relative to the start of the timeline. This capability simplifies the code as follows:

    import javafx.animation.Timeline;
    import javafx.animation.Interpolator;
    
    var x: Number;
    
    Timeline {
      keyFrames: [
             at (0s) {x => 0.0},
             at (4s) {x => 158.0 tween Interpolator.LINEAR}
      ]
    }.play();
    
    
     
  4. Bind the translateX instance variable of the Path object to the x variable as shown in the following code fragment:

    Path{
      ...
      translateX: bind x
      ...
    }
    
     
    When the x variable changes, the translateX variable of the Path object also changes. More details about the Data Binding mechanism are in Applying Data Binding to UI Objects.
Controlling the Timeline Cycle

You can use instance variables of the Timeline class to control the timeline cycle.

  1. Set the repeatCount instance variable to Timeline.INDEFINITE to loop the animation.

  2. Set the autoReverse instance variable to true to enable two-way movement.

The following code accomplishes these tasks:

import javafx.animation.Timeline;
import javafx.animation.Interpolator;

var x: Number;

Timeline {
  repeatCount: Timeline.INDEFINITE
  autoReverse: true  
  keyFrames: [
         at (0s) {x => 0.0},
         at (4s) {x => 158.0 tween Interpolator.LINEAR}
  ]
}.play();

 

The modified code of the application is shown below:

import javafx.animation.Interpolator;
import javafx.animation.Timeline;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.shape.ArcTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;

var x: Number;

Timeline {
  repeatCount: Timeline.INDEFINITE
  autoReverse: true
  keyFrames: [
   at (0s) {x => 0.0},
   at (4s) {x => 158.0 tween Interpolator.LINEAR}
  ]
}.play();

Stage{
    title: "Cloud"
    scene: Scene{
            fill: Color.WHITE
            content:[
                ImageView{
                    image: Image{
                        url: "{__DIR__}sun.jpg"
                    }},
                Path {
                    translateX: bind x
                    translateY: 100
                    stroke: Color.DODGERBLUE
                    fill: LinearGradient {  
                        startX:60, 
                        startY:10, 
                        endX:10 
                        endY:80 , 
                        proportional: false   
                        stops: [  
                            Stop {offset: 0.0 color: Color.DODGERBLUE},  
                            Stop {offset: 0.5 color: Color.LIGHTSKYBLUE},
                            Stop {offset: 1.0 color: Color.WHITE}
                        ]
                    }             
                    elements: [
                        MoveTo {x: 15 y: 15 },
                        ArcTo {x: 50 y: 10 radiusX: 20 radiusY: 20 sweepFlag: true},
                        ArcTo {x: 70 y: 20 radiusX: 20 radiusY: 20 sweepFlag: true},
                        ArcTo {x: 50 y: 60 radiusX: 20 radiusY: 20 sweepFlag: true},
                        ArcTo {x: 20 y: 50 radiusX: 10 radiusY: 5 sweepFlag: true},
                        ArcTo {x: 15 y: 15 radiusX: 10 radiusY: 10 sweepFlag: true},
                    ]   
                }//Path
            ]
     }//Scene
}//Stage
 

When compiled and run this code produces the following window:

This animation application uses linear interpolation which moves the object in even time increments.  You can play with other forms of interpolation. For example, if you set the Interpolator.EASEBOTH type, the cloud will slightly slow down at the start and at the end of the timeline cycle.

Adding Vertical Motion

You can enhance the application by creating the originally desired floating effect.

  1. Create another timeline for the y coordinate of the shape.

  2. Bind the translateY instance variable to the y value as shown on the following code fragment:

    var y: Number;
    
    Timeline {
      repeatCount: Timeline.INDEFINITE
      autoReverse: true
      keyFrames: [
        at (0s) {y => 0.0},
        at (7s) {y => 258.0 tween Interpolator.LINEAR},
      ]
    }.play();
    ...
    
    Path{
       ...
       translateY: bind y
       ...
    }//Path
    
    
     

    Note: The y variable attains its maximum position after seven seconds, which is faster than the x variable. Therefore, the translateY value changes faster than translateX. This produces a wandering effect.

    The following is the complete code of the example.

    import javafx.animation.Interpolator;
    import javafx.animation.Timeline;
    import javafx.stage.Stage;
    import javafx.scene.Scene;
    import javafx.scene.paint.Color;
    import javafx.scene.paint.LinearGradient;
    import javafx.scene.paint.Stop;
    import javafx.scene.image.Image;
    import javafx.scene.image.ImageView;
    import javafx.scene.shape.ArcTo;
    import javafx.scene.shape.MoveTo;
    import javafx.scene.shape.Path;
    
    var x: Number;
    
    Timeline {
      repeatCount: Timeline.INDEFINITE
      autoReverse: true
      keyFrames: [
       at (0s) {x => 0.0},
       at (4s) {x => 158.0 tween Interpolator.LINEAR}
      ]
    }.play();
    
    var y: Number;
    
    Timeline {
      repeatCount: Timeline.INDEFINITE
      autoReverse: true
      keyFrames: [
        at (0s) {y => 0.0},
        at (7s) {y => 258.0 tween Interpolator.LINEAR},
      ]
    }.play();
    
    Stage{
        title: "Cloud"
        scene: Scene{
                fill: Color.WHITE
                content:[
                    ImageView{
                        image: Image{
                            url: "{__DIR__}sun.jpg"
                        }},
                    Path {
                        translateX: bind x
                        translateY: bind y
                        stroke: Color.DODGERBLUE
                        fill: LinearGradient {  
                            startX:60, 
                            startY:10, 
                            endX:10 
                            endY:80 , 
                            proportional: false   
                            stops: [  
                                Stop {offset: 0.0 color: Color.DODGERBLUE},  
                                Stop {offset: 0.5 color: Color.LIGHTSKYBLUE},
                                Stop {offset: 1.0 color: Color.WHITE}
                            ]
                        }             
                        elements: [
                            MoveTo {x: 15 y: 15 },
                            ArcTo {x: 50 y: 10 radiusX: 20 radiusY: 20 sweepFlag: true},
                            ArcTo {x: 70 y: 20 radiusX: 20 radiusY: 20 sweepFlag: true},
                            ArcTo {x: 50 y: 60 radiusX: 20 radiusY: 20 sweepFlag: true},
                            ArcTo {x: 20 y: 50 radiusX: 10 radiusY: 5 sweepFlag: true},
                            ArcTo {x: 15 y: 15 radiusX: 10 radiusY: 10 sweepFlag: true},
                        ]   
                    }//Path
                ]
         }//Scene
    }//Stage
    
    
     
    When compiled and run, this code produces the following window.

    Cloud bouncing within window
    Figure 5: The application run on the mobile emulator
     

Desktop Profile

 

You can apply one of the effects available in the javafx.scene.effect and javafx.scene.effect.light packages to visually enhance the application. These packages exist only in the desktop profile. Perform the following steps to create the lighting effect and make the cloud seem embossed.

  1. Add import statements for the javafx.scene.effect.Lighting and javafx.scene.effect.light.DistantLight classes

  2. Apply the following code for the effect instance variable of the Path object.

    effect: Lighting{light: DistantLight{azimuth: 90}}
    
     

This effect simulates lighting up the object with a distant light source. The azimuth instance variable defines the angle of the light source.

The complete code of the desktop-specific application is located in the cloudDesktop.fx file. When compiled and run this code produces the window.

Cloud bouncing within window
Figure 6: Desktop-specific application
 
Conclusion

This lesson described how to create an animated object and examined interpolated animation. Try the concepts and techniques mentioned in this lesson to explore the other animation capabilities of JavaFX SDK.

« Previous 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Next »
 
 
 
 
 Photo of Alla Redko
Alla Redko is a technical writer at Sun. She develops documentation for Java SE and JavaFX. Prior to her assignment to Sun, she worked as a technical writer for 10 years.