Skip to main content
Last updated May 17, 2013 18:29, by Tim Halloran
Feedicon  

Demos

This page describes the demos included in the Swing and SWT distributions of the Timing Framework.


TimingSourceResolution

Android: org.jdesktop.android.animation.demos.TimingSourceResolution
Swing: org.jdesktop.swing.animation.demos.TimingSourceResolution
SWT: org.jdesktop.swt.animation.demos.TimingSourceResolution
The SWT version on Windows 7.

This demo application outputs benchmarks of the available TimingSource implementations. It tests each timer with a period from 1 millisecond to 20 milliseconds and reports how precise the actual period of the timer was (under measured). It is based on the TimingResolution demo discussed in Chapter 12 on pages 288–300 (the section on Resolution) of Filthy Rich Clients (Haase and Guy, Addison-Wesley, 2008), however it uses the timers via their Timing Framework TimingSource implementations rather than directly.

Two timing source configurations are benchmarked:

  • AndroidTimingSource (within the Android UI thread) / SwingTimerTimingSource (within Swing EDT) / SWTTimingSource (within the SWT UI thread) – This timer has the advantage that all calls made from it are within the EDT / UI thread. Depending upon which version (Android, Swing, or SWT) you are running from the correct timing source for calls within the EDT / UI thread is chosen.
  • ScheduledExecutorTimingSource (within timer thread) – This timing source is provided by a util.concurrent and calls from it are within its tread context (a thread it created).

The SWT timer under Windows is limited to a resolution of roughly 15 milliseconds, however, drift correction was added to the SWT timer implementation in version 5.0—which helps precision a lot as seen in the screenshot above. The Swing timer, in recent versions of Java, does not have this limitation, although it did at the time Filthy Rich Clients was published and may still under older versions of Windows.

The Swing version on Windows 7.

The Android timer has some lag but the implementation uses the same drift correction used by the SWT timer. The Android timer is tied to a particular Android Activity.

The Android version on a Samsung Galaxy SII.

If you are changing the period of the timer in your application it is a good idea to run this benchmark and see how your computer does. The default period for the Android, Swing, and SWT timers is 15 milliseconds, which is reasonable for most animations.

FadingButtonTF

Swing: org.jdesktop.swing.animation.demos.FadingButtonTF
SWT: org.jdesktop.swt.animation.demos.FadingButtonTF

A demonstration, at least in Swing, of a button that fades in and out against the checkerboard background. This demo is discussed in Chapter 14 on pages 353–356 of Filthy Rich Clients (Haase and Guy, Addison-Wesley, 2008). It uses a Timing Framework Animator that is setup to run until manually stopped (by pressing the button again) moves backwards from 1 to 0 (then back from 0 to 1) and calls back to the FadingButtonTF class (which is a JButton and implements TimingTarget).

The program must first construct a timing source (in this case a Swing timer with a 15 millisecond period) and set it as the default for all animations.

private static void createAndShowGUI() {
  final TimingSource ts = new SwingTimerTimingSource();
  Animator.setDefaultTimingSource(ts);
  ts.init();
  ...
}

The snippet above is typical in all Swing applications that use animations. The code is thread-safe and can be done in the main thread rather than in the EDT (an example below runs the same code in main). SWT looks the same, but uses an SWTTimingSource.

final TimingSource animationTimer = new SWTTimingSource(display);
Animator.setDefaultTimingSource(animationTimer);
animationTimer.init();

Next the animation is constructed.

Animator f_animator;
...
f_animator = new Animator.Builder()
  .setRepeatCount(Animator.INFINITE)
  .setStartDirection(Direction.BACKWARD)
  .addTarget(this)
  .build();

Note the use of the builder patter to construct the animation, this is one of the API changes since Filthy Rich Clients was published.

When the button is pressed the animation is started or stopped.

public void actionPerformed(ActionEvent ae) {
  if (!f_animator.isRunning()) {
    this.setText("Stop Animation");
    f_animator.start();
  } else {
    f_animator.stop();
    this.setText("Start Animation");
    // reset alpha to opaque
    f_alpha = 1.0f;
  }
}

The f_alpha field controls the translucency of the button. The value of the f_alpha field is changed when the animator calls back at each "tick" of the timer when the animation is running.

@Override public void timingEvent(Animator source, double fraction) {
  f_alpha = (float) fraction;
  // redisplay our button
  repaint();
}

The SWT FadingButtonTF demo is different than the Swing demo because SWT doesn't support translucent controls (at all). Instead of the button, the SWT demo fades the red squares on the checkerboard in and out of view instead. The callback in the demo, shown below, changes the color rather than the alpha.

final TimingTarget target = new TimingTargetAdapter() {
  Color c;
  @Override public void timingEvent(Animator source, double fraction) {
    if (c != null) c.dispose();
    // fade the red color
    int red = (int) (fraction * 255);
    Color c = new Color(display, new RGB(red, 0, 0));
    f_squareColor = c;
    checkerboard.redraw();
  }
};

As this demo illustrates there are lots of settings on an animation that a program can be set, typically via calls to the Animator.Builder used to construct the animation. These settings and their defaults are described in the table below.

Animator.Builder MethodDescriptionDefault
addTarget(TimingTarget)gets timing events while the animation is runningnone
setDefaultTimingSource(TimingSource) (static method on Animator) or Animator.Builder(TimingSource) (constructor)a timing source for the animationnone
setDuration(long, TimeUnit)the duration of one cycle of the animation1 second
setEndBehavior(Animator.EndBehavior)what happens at the end of the animationAnimator.EndBehavior.HOLD
setInterpolator(Interpolator)the interpolator for each animation cycleLinearInterpolator
setRepeatBehavior(Animator.RepeatBehavior)the repeat behavior of the animationAnimator.RepeatBehavior.REVERSE
setRepeatCount(long)the number of times the animation cycle will repeat1
setStartDirection(Animator.Direction)the start direction for the initial animation cycleAnimator.Direction.FORWARD

RaceBasic

Swing: org.jdesktop.swing.animation.demos.RaceBasic
SWT: org.jdesktop.swt.animation.demos.RaceBasic

The simplest version of the race track animation. It sets up an animation to move the car from one position to another, linearly, over a given time period.

The program first constructs a Swing timer timing source with a 15 millisecond period and sets it as the default for all animations.

public static void main(String args[]) {
  ...
  TimingSource ts = new SwingTimerTimingSource();
  Animator.setDefaultTimingSource(ts);
  ts.init();
  Runnable doCreateAndShowGUI = new Runnable() {
    public void run() {
      new RaceBasic("Swing Race (Basic)");
    }
  };
  SwingUtilities.invokeLater(doCreateAndShowGUI);
}

The animation used to drive the car on the first leg of the track is obtained from a method. A method is used so that the RaceBasicNonLinear demo can override how the animation is created.

protected static final int RACE_TIME = 2;
...
protected Animator getAnimator() {
  return new Animator.Builder()
    .setDuration(RACE_TIME, TimeUnit.SECONDS)
    .addTarget(this)
    .build();
}

This demo is discussed in Chapter 14 on pages 357–359 of Filthy Rich Clients (Haase and Guy, Addison-Wesley, 2008). In the book it is referred to as BasicRace rather than RaceBasic. Chet's demo has been augmented with control buttons that allow you to interact with the running animation. In addition to starting and stopping the animation, you can reverse, pause, and restart the animation as well.

RaceBasicNonLinear

Swing: org.jdesktop.swing.animation.demos.RaceBasicNonLinear
SWT: org.jdesktop.swt.animation.demos.RaceBasicNonLinear

Simple subclass of RaceBasic that uses a AccelerationInterpolator to give a non-linear motion effect to the car's movement. The only real substantive difference is the implementation of the getAnimator method.

@Override protected Animator getAnimator() {
  return new Animator.Builder()
    .setDuration(RACE_TIME, TimeUnit.SECONDS)
    .setInterpolator(new AccelerationInterpolator(0.5, 0.2))
    .addTarget(this)
    .build();
}

The AccelerationInterpolator is an API change since Filthy Rich Clients was published. In the original Timing Framework any animation could set an acceleration / declaration, although it was typically only used with linear interpolation. Moving this functionality from Animator to an Interpolator makes it more consistent with other non-linear interpolators, such as SplineInterpolator.

This demo is discussed in Chapter 14 on pages 363–364 of Filthy Rich Clients (Haase and Guy, Addison-Wesley, 2008). In the book it is referred to as NonLinearRace rather than RaceBasicNonLinear. Chet's demo has been augmented with control buttons that allow you to interact with the running animation. In addition to starting and stopping the animation, you can reverse, pause, and restart the animation as well.

SplineInterpolatorTest

Swing: org.jdesktop.swing.animation.demos.SplineInterpolatorTest
SWT: org.jdesktop.swt.animation.demos.SplineInterpolatorTest

A demo application that compares the elapsed fraction, in real time, to the elapsed fraction when using a sampleSplineInterpolator. The creation and start of the animation is shown in the below snippet of code.

SplineInterpolator si = new SplineInterpolator(1, 0, 0, 1);
Animator animator = new Animator.Builder()
  .setDuration(DURATION, TimeUnit.MILLISECONDS)
  .setInterpolator(si)
  .addTarget(new SplineInterpolatorTest())
  .build();
animator.start();

This is based upon the TimingResolution demo discussed in Chapter 14 on pages 372–375 (the section on Resolution) of Filthy Rich Clients (Haase and Guy, Addison-Wesley, 2008), however it has been changed in several ways. First, it outputs to a window. Second, it uses a global timer, as advocated by Haase in his JavaOne 2008 talk, which causes the "ticks" to be slightly off from what is shown in the book (sometimes—you might get lucky).

Triggers

Swing: org.jdesktop.swing.animation.demos.Triggers
SWT: org.jdesktop.swt.animation.demos.Triggers

Simple program that demonstrates the use of several different triggers available in the Timing Framework. The API for triggers has been significantly changed since Filthy Rich Clients was published.

The code to create the five triggers in the demo is shown below.

TriggerUtility.addEventTrigger(triggerButton, SWT.Selection, action.getAnimator());
TriggerUtility.addFocusTrigger(triggerButton, focus.getAnimator(), FocusTriggerEvent.IN, true);
TriggerUtility.addMouseTrigger(triggerButton, armed.getAnimator(), MouseTriggerEvent.PRESS);
TriggerUtility.addMouseTrigger(triggerButton, over.getAnimator(), MouseTriggerEvent.ENTER, true);
TriggerUtility.addTimingTrigger(action.getAnimator(), timing.getAnimator(), TimingTriggerEvent.STOP);
The creation of triggers has been moved into a TriggerUtility class. An implementation exists for Swing and SWT because this portion of the Timing Framework is highly tied to the GUI framework.

This demo is discussed in Chapter 15 on pages 388–391 of Filthy Rich Clients (Haase and Guy, Addison-Wesley, 2008). One significant change has been made to the demo's behavior, the focus trigger (line 2 above) and mouse trigger (line 4 above) auto-reverse the animation when the opposite event to the one they trigger on occurs. For example, the 4th ball moves down to the bottom of the window when the mouse enters the button labeled Trigger, but the ball starts moving back up if the mouse exits the button's area.

TwoButtonShimmy

Swing: org.jdesktop.swing.animation.demos.TwoButtonShimmy
SWT: org.jdesktop.swt.animation.demos.TwoButtonShimmy

This demo animates two buttons across the screen. The animations are started by triggers. The first trigger moves sets up movement the second slowly changes the color of the button text if the mouse is hovering on the button.

The setup code for these animations and their associated triggers is shown in the listing below.

/*
 * Creating JButton with a infinite number of back and forth shimmy
 * repetitions
 */
JButton btnInfinite = new JButton("Infinite");
btnInfinite.setBounds(10, 10, 130, 30);
// Movement on click
TimingTarget ttInfinite = PropertySetter.getTarget(btnInfinite, "location", new Point(10, 10), new Point(250, 10));
Animator animatorInfinite = new Animator.Builder().setRepeatCount(Animator.INFINITE).setDuration(3, SECONDS)
    .addTarget(ttInfinite).build();
TriggerUtility.addActionTrigger(btnInfinite, animatorInfinite);
// Red text color on mouse hover
TimingTarget ttInfinite2 = PropertySetter.getTarget(btnInfinite, "foreground", Color.BLACK, Color.RED);
Animator animatorInfinite2 = new Animator.Builder().setDuration(2, SECONDS).addTarget(ttInfinite2).build();
TriggerUtility.addMouseTrigger(btnInfinite, animatorInfinite2, MouseTriggerEvent.ENTER, true);

/*
 * Creating JButton with one back and forth shimmy
 */
JButton btnFinite = new JButton("Once");
btnFinite.setBounds(10, 50, 130, 30);
// Movement on click
TimingTarget ttFinite = PropertySetter.getTarget(btnFinite, "location", new Point(10, 50), new Point(250, 50),
    new Point(10, 50));
Animator animatorFinite = new Animator.Builder().setDuration(6, SECONDS).addTarget(ttFinite).build();
TriggerUtility.addActionTrigger(btnFinite, animatorFinite);
// Red text color on mouse hover
TimingTarget ttFinite2 = PropertySetter.getTarget(btnFinite, "foreground", Color.BLACK, Color.RED);
Animator animatorFinite2 = new Animator.Builder().setDuration(2, SECONDS).addTarget(ttFinite2).build();
TriggerUtility.addMouseTrigger(btnFinite, animatorFinite2, MouseTriggerEvent.ENTER, true);

ClickAndGo

Swing: org.jdesktop.swing.animation.demos.ClickAndGo
SWT: org.jdesktop.swt.animation.demos.ClickAndGo

This demo uses a property setter to change the property of an object over time. It demonstrates a "to" animation. A "to" animation uses the getter on the property to set the starting point of the animation. This demo did not appear in Filthy Rich Clients, the book did not have a demo that used a "to" animation.

This demo also constructs and disposes of its TimingSource every animation that is done. This demo is an example where long periods of time can elapse between animations and we do not want the TimingSource to "tick" away. This would be best practice for a program that runs animations infrequently.

The user clicks a point in the window and the ball and the colored square move to that location. They use different interpolators but should arrive at the destination point at the same time. The size and color of the rectangle are animated as well as the location.

The code that sets up the animation via a PropertySetter is shown below.

public void mouseClicked(MouseEvent e) {
  if (f_ball.animator != null)  f_ball.animator.stop();

  SwingTimerTimingSource animationTimer = new SwingTimerTimingSource();
  animationTimer.init();
  animationTimer.addPostTickListener(new PostTickListener() {
    @Override public void timingSourcePostTick(TimingSource source, long nanoTime) {
      ClickAndGo.this.repaint();
    }
  });
  f_ball.animator = new Animator.Builder(animationTimer).setDuration(2, TimeUnit.SECONDS).setDisposeTimingSource(true).build();
  final Point clickPoint = new Point(e.getX(), e.getY());
  f_ball.animator.addTarget(PropertySetter.getTargetTo(f_ball, "location", new AccelerationInterpolator(0.5, 0.5), clickPoint));
  final int rectSize = f_ball.image.getWidth();
  final Rectangle clickRect = new Rectangle(e.getX(), e.getY(), rectSize * (f_die.nextInt(4) + 1), rectSize * (f_die.nextInt(4) + 1));
  f_ball.animator.addTarget(PropertySetter.getTargetTo(f_ball, "rect", clickRect));
  final Color rectColor = new Color(f_die.nextInt(255), f_die.nextInt(255), f_die.nextInt(255));
  f_ball.animator.addTarget(PropertySetter.getTargetTo(f_ball, "rectColor", rectColor));
  f_ball.animator.start();
}

When the mouse is clicked any old animation is stopped (line 2 above). A new SwingTimerTimingSource is constructed and a post tick listener is added to repaint the view after each tick. An Animation is constructed (line 11 above) and set to automatically dispose its timing source when it ends via the call setDisposeTimingSource(true).

The first property setter (line 13 above) calls getLocation on f_ball to determine the starting location of the ball and setLocation to change the ball's location as the animation runs. The second property setter (line 16 above) changes the size of the rectangle around the ball. It calls getRect on f_ball to get the starting size of the rectangle and setRect to change the size of the rectangle as the animation runs. The third property setter (line 18 above) changes the fill color of the rectangle around the ball. It calls getRectColor on f_ball to get the starting fill color and setRectColor to change the fill color as the animation runs. Java reflection is used to make the calls on f_ball.

The API for PropertySetter has changed significantly since the publication of Filthy Rich Clients. The PropertySetter class is now a utility that contains several getTarget and getTargetTo methods. These methods return TimingTarget objects that can be added to an animation to change the specified property when the animation is run. The new API is simpler and more flexible (in particular with regard to "to" animations).

DiscreteInterpolationTest

Swing: org.jdesktop.swing.animation.demos.DiscreteInterpolationTest
SWT: org.jdesktop.swt.animation.demos.DiscreteInterpolationTest

An applcation that demonstrates use of a DiscreteInterpolator using KeyFrames within a property setter-based animation. This demo is discussed in Chapter 15 on page 410 of Filthy Rich Clients (Haase and Guy, Addison-Wesley, 2008).

final KeyFrames<Integer> keyFrames = new KeyFrames.Builder<Integer>()
  .addFrames(2, 6, 3, 5, 4)
  .setInterpolator(DiscreteInterpolator.getInstance()).build();

The program first constructs a set of key frames (lines 1–3 above). The value at each frame is an integer. There are 5 frames defined and a DiscreteInterpolator is used between each of them. The builder is flexible, and the snippet above could also have been written as shown below.

final KeyFrames<Integer> keyFrames = new KeyFrames.Builder<Integer>(2)
  .addFrame(6)
  .addFrame(3)
  .addFrame(5)
  .addFrame(4)
  .setInterpolator(DiscreteInterpolator.getInstance())
  .build();

More complex forms of the addFrame method allows you to set the time when the value is taken and how to interpolate up to that value. However, in this demo this complexity is not needed. Note in the code above, however that the first key value can be set in the KeyFrames.Builder constructor.

Next the demo iterates through the key frames and outputs information about each frame.

out("Constructed Key Frames");
out("----------------------");
int i = 0;
for (KeyFrames.Frame<Integer> keyFrame : keyFrames) {
  final String s = keyFrame.getInterpolator() == null ? "null" : keyFrame.getInterpolator().getClass().getSimpleName();
  out(String.format("Frame %d: value=%d timeFraction=%f interpolator=%s", i++, keyFrame.getValue(), keyFrame.getTimeFraction(), s));
}

Iterating through the key frames object produces the below output. The ability to interrogate each frame is a new feature that was not in the API when Filthy Rich Clients was published. In addition to iteration, a get method can be used to obtain a frame by its index.

Constructed Key Frames
----------------------
Frame 0: value=2 timeFraction=0.000000 interpolator=null
Frame 1: value=6 timeFraction=0.250000 interpolator=DiscreteInterpolator
Frame 2: value=3 timeFraction=0.500000 interpolator=DiscreteInterpolator
Frame 3: value=5 timeFraction=0.750000 interpolator=DiscreteInterpolator
Frame 4: value=4 timeFraction=1.000000 interpolator=DiscreteInterpolator

Notice that the time fraction when the key value occurs was automatically computed for the demo. It can be explicitly set using the addFrame method, if necessary. Also notice that the interpolator for frame 0 is null. This is because the interpolator is used to interpolate from the previous frame up to the frame it is stored with, and frame 0 has no previous frame.

Finally, the program constructs an animation that outputs the changing values.

final Animator animator = new Animator.Builder()
  .setDuration(3, TimeUnit.SECONDS)
  .addTarget(PropertySetter.getTarget(object, "intValue", keyFrames))
  .addTarget(object)
.build();
out("");
out("Animation of intValue");
out("---------------------");
animator.start();

The animation uses a property setter that calls setIntValue that method is shown below.

public void setIntValue(int intValue) {
  f_intValue = intValue;
  out("intValue = " + f_intValue);
}

The animation produces the below output.

Animation of intValue
---------------------
intValue = 2
intValue = 2
intValue = 2
intValue = 2
intValue = 2
intValue = 2
intValue = 2
intValue = 6
intValue = 6
intValue = 6
intValue = 6
intValue = 6
intValue = 6
intValue = 6
intValue = 3
intValue = 3
intValue = 3
intValue = 3
intValue = 3
intValue = 3
intValue = 3
intValue = 5
intValue = 5
intValue = 5
intValue = 5
intValue = 5
intValue = 5
intValue = 5
intValue = 4

The output illustrates how DiscreteInterpolator works. It interpolates fractional values to animate movement in "discrete" steps. A discrete animation is defined to be one where the values during an animation do not change smoothly between the boundary values, but suddenly, at the boundary points.

RaceCompleteMultiStep

Swing: org.jdesktop.swing.animation.demos.RaceCompleteMultiStep
SWT: org.jdesktop.swt.animation.demos.RaceCompleteMultiStep

The full-blown demo with all of the bells and whistles. This one uses the facilities shown in all of the other variations, but adds both multi-step and non-linear interpolation. It does this by creating a KeyFrames object to hold the times / values / splines used for each segment of the race. It also adds an animation for the rotation of the car (since the car should turn as it goes around the curves) and sound effects (just to go completely overboard).

The demo first constructs a single animator to drive the car's position, the car's rotation, and the sound effects.

animator = new Animator.Builder()
  .setDuration(RACE_TIME, TimeUnit.MILLISECONDS)
  .setRepeatCount(Animator.INFINITE)
  .setRepeatBehavior(RepeatBehavior.LOOP)
  .build();

The construction of the key frames object to drive the car is shown below.

 Point values[] = {
  RaceTrackView.START_POS,
  RaceTrackView.FIRST_TURN_START, RaceTrackView.FIRST_TURN_END,
  RaceTrackView.SECOND_TURN_START, RaceTrackView.SECOND_TURN_END,
  RaceTrackView.THIRD_TURN_START, RaceTrackView.THIRD_TURN_END,
  RaceTrackView.FOURTH_TURN_START, RaceTrackView.START_POS };
// For realistic movement, we want a big acceleration on the straightaways.
Interpolator initialSpline = new SplineInterpolator(1.00f, 0.00f, 0.2f, .2f);
Interpolator straightawaySpline = new SplineInterpolator(0.50f, 0.20f, .50f, .80f);
Interpolator curveSpline = new SplineInterpolator(0.50f, 0.20f, .50f, .80f);
Interpolator finalSpline = new SplineInterpolator(0.50f, 0.00f, .50f, 1.00f);
Interpolator[] interps = { null, initialSpline, curveSpline, straightawaySpline,
  curveSpline, straightawaySpline, curveSpline, straightawaySpline, finalSpline };

final KeyFrames.Builder<Point> builder = new KeyFrames.Builder<Point>(values[0]);
for (int i = 1; i < values.length; i++) {
  builder.addFrame(values[i], times[i], interps[i]);
}
final KeyFrames<Point> keyFrames = builder.build();

A property setter uses these key frames to animation the cars position by invoking setCarPosition.

final TimingTarget modifier = PropertySetter.getTarget(basicGUI.getTrack(), "carPosition", keyFrames);
animator.addTarget(modifier);

The rotation of the car is setup in a similar manner.

int[] rotationKeyValues = { 360, 315, 270, 225, 180, 135, 90, 45, 0 };
Interpolator straightawayTurnSpline = new SplineInterpolator(1.0f, 0.0f, 1.0f, 0.0f);
Interpolator curveTurnSpline = new SplineInterpolator(0.0f, 0.5f, 0.5f, 1.0f);
Interpolator[] rotationInterps = { null, straightawayTurnSpline, curveTurnSpline, straightawayTurnSpline,
  curveTurnSpline, straightawayTurnSpline, curveTurnSpline, straightawayTurnSpline, curveTurnSpline };
final KeyFrames.Builder<Integer> rotationBuilder = new KeyFrames.Builder<Integer>(rotationKeyValues[0]);
for (int i = 1; i < values.length; i++) {
  rotationBuilder.addFrame(rotationKeyValues[i], times[i], rotationInterps[i]);
}
KeyFrames<Integer> rotationKeyFrames = rotationBuilder.build();
final TimingTarget rotationModifier = PropertySetter.getTarget(basicGUI.getTrack(), "carRotation", rotationKeyFrames);
animator.addTarget(rotationModifier);

Finally, the sound effects are added as a timing target of the same animation.

soundEffects = new RaceSoundEffects(rotationKeyFrames);
animator.addTarget(soundEffects);

This demo is discussed in Chapter 15 on pages 414–419 of Filthy Rich Clients (Haase and Guy, Addison-Wesley, 2008). In the book it is referred to as MultiStepRace rather than RaceCompleteMultiStep.

TooManyBallsBroken

Swing: org.jdesktop.swing.animation.demos.TooManyBallsBroken

This demonstration is a variant of the demonstration by Chet Haase at JavaOne 2008. Chet discussed the problem in the original Timing Framework where, by default, each animation used its own javax.swing.Timer. This did not scale and, as balls were added to the demonstration, the multiple timers caused noticeable performance problems—the FPS plummets and the animation starts to look like a slide show.

This version of the "Too Many Balls!" demonstration mimics the problem Chet demonstrated by explicitly creating a Swing timing source for each animation. Note that the Timing Framework allows setting a default timer on the Animator.Builder, thus making it easy for client code to avoid this problem. This design approach, in fact, was inspired by the JavaOne 2008 talk.

The "broken" snippet is shown below. A new Swing timing source is constructed and then passed into the Animator.Builder constructor. This approach is not recommended and has, as the demo illustrates, severe performance problems.

ball.ts = new SwingTimerTimingSource();
ball.ts.init();
ball.animator = new Animator.Builder(ball.ts)
  .setDuration(duration, TimeUnit.SECONDS)
  .addTarget(circularMovement)
  .setRepeatCount(Animator.INFINITE)
  .setRepeatBehavior(Animator.RepeatBehavior.LOOP)
  .setInterpolator(i)
  .build();
ball.animator.start();

Is it ever correct to pass in a timing source to the Animator.Builder constructor rather than setting a global timing source? Yes, if your application has very few animations, one or two, then you can manage an individual timing source for each animation. This allows you to dispose of the timing source when its animation is finished—rather than leaving a timing source ticking away for the entire duration of your program.

The SWT version of the Timing Framework never exhibited this behavior. This is because SWT, in effect, has a global timer attached to the Display object. Hence, there is no version of this broken demo for SWT.

TooManyBalls

Swing: org.jdesktop.swing.animation.demos.TooManyBalls
SWT: org.jdesktop.swt.animation.demos.TooManyBalls

This is a version of the demonstration by Chet Haase at JavaOne 2008 that has been updated to use the current Timing Framework API.

The code to add a ball to the display is shown below. It uses two key frames to animate the ball in an oval. One key frame drives the x position of the ball. The other key frame drives the y position of the ball. Notice that the interpolator for each frame is explicitly set in the call to addFrame to create the circular movement. A die is used to randomize the shape of the oval, the duration of the animation, and if the ball moves at a constant rate or uses an AccelerationInterpolator.

private static final Interpolator ACCEL_4_4 = new AccelerationInterpolator(0.4, 0.4);
private static final Interpolator SPLINE_0_1_1_0 = new SplineInterpolator(0.00, 1.00, 1.00, 1.00);
private static final Interpolator SPLINE_1_0_1_1 = new SplineInterpolator(1.00, 0.00, 1.00, 1.00);

private void addBall() {
  final Ball ball = new Ball();
  ball.imageIndex = f_die.nextInt(5);
  BufferedImage ballImage = f_ballImages[ball.imageIndex];

  ball.x = f_die.nextInt(f_panel.getWidth() - ballImage.getWidth());
  ball.y = f_die.nextInt(f_panel.getHeight() - ballImage.getHeight());

  final int duration = 4 + f_die.nextInt(10);

  // Create a circular movement.
  int radiusX = f_die.nextInt(400);
  if (f_die.nextBoolean())
    radiusX = -radiusX;
  int radiusY = f_die.nextInt(300);
  if (f_die.nextBoolean())
    radiusY = -radiusY;
  KeyFrames.Builder<Integer> builder = new KeyFrames.Builder<Integer>(ball.x);
  builder.addFrame(ball.x + radiusX, SPLINE_0_1_1_0);
  builder.addFrame(ball.x, SPLINE_1_0_1_1);
  builder.addFrame(ball.x - radiusX, SPLINE_0_1_1_0);
  builder.addFrame(ball.x, SPLINE_1_0_1_1);
  final KeyFrames<Integer> framesX = builder.build();

  builder = new KeyFrames.Builder<Integer>(ball.y);
  builder.addFrame(ball.y + radiusY, SPLINE_1_0_1_1);
  builder.addFrame(ball.y + (2 * radiusY), SPLINE_0_1_1_0);
  builder.addFrame(ball.y + radiusY, SPLINE_1_0_1_1);
  builder.addFrame(ball.y, SPLINE_0_1_1_0);
  final KeyFrames<Integer> framesY = builder.build();

  final TimingTarget circularMovement = new TimingTargetAdapter() {
    @Override
    public void timingEvent(Animator source, double fraction) {
      ball.x = framesX.getInterpolatedValueAt(fraction);
      ball.y = framesY.getInterpolatedValueAt(fraction);
    }
  };
  // Sometimes go at a constant rate, sometimes accelerate and decelerate.
  final Interpolator i = f_die.nextBoolean() ? ACCEL_4_4 : null;
  ball.animator = new Animator.Builder()
    .setDuration(duration, TimeUnit.SECONDS)
    .addTarget(circularMovement)
    .setRepeatCount(Animator.INFINITE)
    .setRepeatBehavior(Animator.RepeatBehavior.LOOP)
    .setInterpolator(i)
    .build();
  ball.animator.start();

  f_balls.add(ball);
}

This demo uses the rendering framework provided as part of the Timing Framework. By default passive rendering is used in both Swing and SWT. In the Swing version of the demo active rendering may be used (SWT doesn't support active rendering). To use active rendering set the org.jdesktop.renderer.active system property to any value. For example place -Dorg.jdesktop.renderer.active=true on the java command line.

SplineEditor

Swing: org.jdesktop.swing.animation.demos.SplineEditor

The Swing distribution also includes the Spline Editor (from Chapter 14 of Filthy Rich Clients) which can be used to design and preview a particular spline interpolation.

 
 
Close
loading
Please Confirm
Close