Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

JavaWorld Daily Brew

Programming Animations in FX

 

I've been trying to refactor the animation support in FX lately and discussed it with Brian Goetz while he was here, and here's the current state. These changes will be checked in to the OpenJFX repository shortly. Note: this about programming animations, not tool-support. I assume a tool will produce data structures that describe canned animations, not code like this.

My goal is to make it possible for the programmer to specify the required behavior and still be able to actually understand what his code is doing.

Animations are fundamentally updates. They assign values to properties (or invoke processes that do such) at certain time instants.

The traditional animation loop

while (animationIsRunning) {
    // update stuff
    wait(interval);
}

is easy to understand, but it has several drawbacks:

  1. It requires a dedicated thread - introducing data synchronization issues
  2. It doesn't handle jitter - "update stuff" can take variable amounts of time at each iteration, making the animation look choppy.
  3. Since the time consumed by "update stuff" is included in the loop, the animation will exhibit different behavior based on the speed of the processor.

FX provides a time iterated for loop which (I think) is as easy to understand but overcomes these limitations:

println("before the animation");

for (i in [1..10] dur 10000) {
     // update stuff
}

println("after the animation");

The above loop describes a discrete animation which executes every second for 10 seconds. In addition, the loop body is always executed in the current thread. However the loop still blocks until all 10 iterations are complete. Currently, this is implemented with the Event Queue Hack, but proper support for continuations could be provided via code generation by the FX compiler. Since the loop blocks, it can be intuitively incorporated into a larger sequential process, e.g;

Rect {
     fill: green
     height: 50
     width:  50
     var x = 0
     var y = 0
     x: bind x
     y: bind y
     onMouseClicked: operation(e) {
          for (i in [0..100] dur 1000) {
               x = i;
          }
          for (i in [0..100] dur 1000 {
               y = i;
          }
     }
}

The above moves the rectangle from (0, 0) to (100, 0) over 1 second, and then moves it from (100, 0) to (100, 100) over 1 second, when you click the mouse on the rectangle.

Now, sometimes we want animations to run in parallel. For this case, a nearly identical construct is provided, namely the update trigger:

trigger on (i = [1..10] dur 10000) {
      // update stuff
}

println("after the trigger, but the animation is still running");

In this case the body of the trigger is executed 10 times, once per second. However, unlike the for loop, the code after the trigger executes in parallel with the trigger's body. The body of the trigger is still executed in the same thread, however, when the timer triggers it:

Rect {
     fill: green
     height: 50
     width:  50
     var x = 0
     var y = 0
     x: bind x
     y: bind y
     onMouseClicked: operation(e) {
          trigger on (i = [0..100] dur 1000) {
               x = i;
          }
          trigger on (i = [0..100] dur 1000) {
               y = i;
          }
     }
}

In this example, the animations of x and y occur in parallel and the the rectangle moves diagonally from (0, 0) to (100, 100) over 1 second.

In the case of discrete animations such as these the "frame rate" for the loop body is duration/(sizeof input-1). For cases where an animation is conceptually continuous, a better approach is often to use a predefined (high) frame rate and interpolate between the values for each frame using some interpolation function. The syntax is open to discussion but here's how that works right now:

Rect {
     fill: green
     height: 50
     width:  50
     var x = 0
     var y = 0
     x: bind x
     y: bind y
     onMouseClicked: operation(e) {
          trigger on (i = [0, 100] dur 1000 motion LINEAR) {
               x = i;
          }
          trigger on (i = [0, 100] dur 1000 motion LINEAR) {
               y = i;
          }
     }
}

LINEAR is a predefined function that performs linear interpolation (you can plug in your own instead). The interpolation function is of the form

function<T> (inputValues:T*, elapsedTimeAsUnitInterval: Number): T

As a shorthand for animating individual properties a form of the the assignment statement is also provided:

x = [0, 100] dur 1000 motion LINEAR;
y = [0, 100] dur 1000 motion LINEAR;

which is equivalent to the two triggers above in onMouseClicked().

Finally, for the case where the for loop actually doesn't do anything:

for (i in [0, 1] dur 1000) {
     // don't do anything
}

we have a shorthand called "wait"

wait(1000);

Below are a few test cases for these constructs:

  1. Video Poker
    Standalone
    FXPad
  2. Motorola Intro
    Standalone
    FXPad
  3. Simple Path Animation
    FXPad