 |
-- Main.sfshaza - 27 Jun 2008
Animation, the Easy Way!
If you've ever programmed animations and thought "there must be an easier way",
then today's blog entry is sure to bring welcomed relief.
The JavaFXTM Script programming language now supports
key frame animation, which allows you to declare
your animations using a simple syntax that resembles plain English.
All graphics rendering now happens automatically, behind the
scenes, allowing you to focus on the animation at hand rather
than the "plumbing" to make it all work.
To demonstrate this functionality we will re-create the classic "tumbling
duke" animation. Since this may be your
first exposure to the language, we'll break this discussion up into
two separate entries: today we will cover the basics;
early next week we'll cover everything else.
You'll want to try this code for yourself, but fear not, the development
environment is easy to set up.
The edit-compile-run process is similar to that of
the JavaTM programming language,
but the commands are
javafxc and javafx instead of
javac and java.
There is also a JavaFX Script compiler plug-in available for the NetBeans IDE, for
those who prefer not to work from the command line.
For instructions on setting up any of these environments, visit the
openjfx project page
at http://openjfx.dev.java.net.
Having said that, let's get animating!
As you may already know, the tumbling duke animation
is comprised of 17 separate images.
Traditionally, creating this animation has required an intimate understanding of the AWT and Swing painting mechanisms (if you haven't seen it before, take a
look at the original Tumbling Duke source code.)
As you will soon learn, accomplishing the same end result using JavaFX Script technology is a simple matter once you've mastered its syntax.
import javafx.ui.*;
import javafx.ui.canvas.*;
import javafx.animation.*;
// Load image data
var images = for (i in [1..17]) {
Image { url: "file:images/T{i}.gif"};
}
...
|
Listing 1: Importing Classes and Loading Images
Our first task is to load the images into memory. If your initial instinct
is to place these images into an array, you're right, although here that data structure is known as a sequence. Sequences differ from Java programming language arrays, but are similar in that
they also hold a list of objects.
Sequences
are generally declared with square brackets (the code [1..17] is a sequence), but the system will also infer this based on context when needed.
Such is the case with our var images declaration.
The for loop that follows
returns one javafx.ui.Image object per iteration;
when it's done, you end up with an images variable
that contains all of the image data.
(If you're wondering where the constructor
call is, the JavaFX Script programming language uses object literals; the code
Image{} is enough to return a new Image object.)
...
// Create timeline and key animation frames
var t = Timeline {
keyFrames: for (image in images) {
KeyFrame {
time: 100ms* indexof image
action: function(){currDuke=image;}
}
}
}
...
|
Listing 2: Creating the Key Frames and Animation Timeline
Next, we'll create the animation's key frames and place them on a timeline. These constructs are
represented by the javafx.animation.KeyFrame and javafx.animation.Timeline classes,
respectively. In the interest of saving space we've rolled a lot of functionality into the above code,
so let's take a minute to make sure you understand what's going on.
In the JavaFX Script programming language, an object's state is
stored in its attributes.
Above we initialize the keyFrames attribute
of the Timeline class, followed by the time and action attributes of the KeyFrame class.
So far as the animation itself is concerned, the important concepts are that a "key frame" (a KeyFrame instance) represents one specific point in time along your animation,
and a Timeline instance holds a sequence of KeyFrame objects. It's up to you to decide where the key frames of your
animation should be; in this case we consider each flip of the image to be one frame in the animation. It seems reasonable that the images
should change 100 milliseconds apart, so we've initialized each key frame with a time value that is 100ms greater than the previous frame.
The action attribute of the KeyFrame class accepts a function; that function can contain any code that you want. Keep in mind that we're looping through each of the images where this occurs, so the only action that we care about is assigning the current image of the current key frame to a
separate
variable named currDuke. As you will see below, this currDuke variable is where the GUI will look to find the current image.
...
// Set first image to be displayed
var currDuke = images[0];
// Create the GUI application frame
Frame {
title: "Tumbling Duke"
width: 400
height: 150
background: Color.WHITE
visible: true
content: FlowPanel {
content: Canvas {
onMouseClicked: function(e) {t.start();}
content: ImageView {
image: bind currDuke;
}
}
}
}
|
Listing 3: Creating the Application GUI
At this point, all that's left to do is to construct the GUI. This is actually the easiest part; all we need to do
is create a few objects and we're done.
The image attribute of the ImageView class is the
actual image that will appear on screen. The code "image: bind currDuke" is the magic that keeps the GUI in sync with the
images used in our timeline. By "binding" the image attribute
to the currDuke variable, the
on-screen image will automatically change whenever the value of currDuke changes.
In this early version of the code, we'll tell the animation timeline to begin only when the user clicks somewhere on the GUI's drawing surface. In the next
blog entry we'll change
that behavior to an infinite loop that begins automatically, thereby replicating the behavior of the original animation.
import javafx.ui.*;
import javafx.ui.canvas.*;
import javafx.animation.*;
// Load image data
var images = for (i in [1..17]) {
Image { url: "file:images/T{i}.gif"};
}
// Create timeline and key animation frames
var t = Timeline {
keyFrames: for (image in images) {
KeyFrame {
time: 100ms* indexof image
action: function(){currDuke=image;}
}
}
}
// Set first image
var currDuke = images[0];
// Create the GUI application frame
Frame {
title: "Tumbling Duke"
width: 400
height: 150
background: Color.WHITE
visible: true
content: FlowPanel {
content: Canvas {
onMouseClicked: function(e) {t.start();}
content: ImageView {
image: bind currDuke;
}
}
}
}
|
Listing 4: The Complete Code Listing
The listing above shows the complete source code so far. Be sure to check back next week for the continuation of this discussion!
-- Scott Hommel
|