Skip to main content

Make Swing... er... Swing!

Posted by gfx on February 7, 2006 at 1:24 AM PST

Most GUI are really boring. And I really mean it. Admit it, you'd rather listen to a French stammerer trying to recite a bad English translation of War and Peace during a rainy Sunday afternoon (and boy what a long afternoon it would be) than look yet again at some applications. Besides cool aesthetics, a way to make a GUI for appealing (just talking about the look here) is to introduce animations. Some get it right (Apple), and some get it really wrong (the first thing I do with a fresh Windows install - after muting the sounds - is to shut down all the animations). But that's not the point. Bad or good, we just want animations in our Swing application.

javax.swing.Timer is the preferred way to animate a component, an effect or whatever in Swing. Despite a lack of precision, no better than the native timing resolution (which would be around 15ms on Windows), this timer is really easy to use as it relies on ActionListener and as it posts all its events in the EDT. Unfortunately, this timer's API is very crude and gives you the bare minimum:

Timer crudeTimer = new Timer(1000 / 30, new ActionListener() {
    public void actionPerformed(ActionEvent e) {
    }
});
crudeTimer.start();

That, is a simple timer firing an event 30 times per second (or so it claims at least.) So, what is missing here? Too many things to make animations easy to develop. First, this timer doesn't track the elapsed time. You have to do that by yourself using System.currentTimeMillis() or better yet System.nanoTime(). This timer also doesn't care about the duration of the animation. You have to test the elapsed time against a given delay and stop the timer accordingly, using this beautiful expression: ((Timer) e).stop(). What about repeating an animation? Going back and forth? You get the point, Timer's useful but way too generic for our purpose. I have written so many Timer/ActionListener pairs full of tangled and ugly Java code that I feel pretty confident to herein conclude about the timers: @#!.

Let me introduce you to the solution of the wise man, Chet Haase's timing framework. Before you run away bewildered by such a powerful imagination to name a framework, take a deep breath, sit down and stay focused for a couple more minutes, will you? It's worth it. I swear. Chet ran into the exact same problems as the aforementioned ones. But Chet is not lazy, Chet is full of resources. Chet built a solution to this problem. Bestow our eternal gratitude on Chet (or whatever that is you want to bestow on Chet.) Anyway, Chet wrote a comprehensive and really interesting introduction to his framework that I urge you to read. I won't delve into the details here but the point is, hitherto you were striving to create animations for Swing UI and now it's much easier. How so? Let's take a look at a simple example a nice looking but boring, static button:


Evidence #1. A boring button.

Usually, Swing developers make buttons more interactive by introducing a rollover effect. The nice thing is you just need to call somethin like JButton.setRolloverIcon() to make it work. Unfortunately, the change is sudden and doesn't look so sexy. The solution is to animate the rollover effect. In my case, I added a highlight in the background that gradually appears when the mouse moves over the button. The animation is vey fast (about 300 ms) so it's not annoying but you can still notice it. Here is the animated result:


Evidence #2. A cool button (and animated)

When the mouse exits the button, the contrary happens, and the highlights fades out. In this particular case, I took care of animation consistency. For instance, if you enter the button then exits halfway throught the animation then enter again, you will see the animation react properly. It won't jump to its extreme values. Believe me, that's a pain to handle with javax.swing.Timer. Now, thanks to Chet's timing framework, things are way better than they used to be:

private final class HiglightHandler extends MouseAdapter {
    private TimingController timer;
    private Cycle cycle = new Cycle(300, 1000 / 30);
    private Envelope envelope = new Envelope(1, 0,
                                             RepeatBehavior.FORWARD,
                                             EndBehavior.HOLD);
   
    @Override
    public void mouseEntered(MouseEvent e) {
        animate(true);
    }
   
    @Override
    public void mouseExited(MouseEvent e) {
        animate(false);
    }

    private void animate(boolean forward) {
        if (timer != null && timer.isRunning()) {
            timer.stop();
        }
        timer = new TimingController(cycle, envelope, new AnimateGhost(true));
        timer.start();
    }
}

private final class AnimateGhost implements TimingTarget {
    private boolean forward;
    private float oldValue;

    AnimateGhost(boolean forward) {
        this.forward = forward;
        oldValue = ghostValue;
    }

    public void timingEvent(long cycleElapsedTime,
                            long totalElapsedTime,
                            float fraction) {
        ghostValue = oldValue + fraction * (forward ? 1.0f : -1.0f);
        repaint();
    }

    public void begin() {
    }

    public void end() {
    }
}

This code might seem a bit long but when you look more closely you'll see it is very easy. You can see a mouse adapter that handle the mouse enter/exit events and start the animation accordingly. All three fields from HiglightHandler uses classes from Chet's timing framework. The Cycle defines the total duration of the animation as well as its number of frames per second. The Envelope describes the animation itself: it is played once, with no initial delay, it goes forward in time (to get a time fraction going from 0.0 to 1.0) and holds its last value when the animation is over. Finally, the TimingTarget is where the nicest thing happens: the timingEvent() callback gives you everything you need to animate your GUI. In our case we just use the fraction (our position in time) and use it to modify the ghostValue. This value is just used as the transparency level of the white highlight you can see in the second screenshot.

As you can see, this small framework makes things easier both for the author of the code but also for the readers. Chet, Chris and I are currently using this framework almost everyday and as we go along, Chet is discovering new things to integrate into it. You can expect really nice things in a near future, like non-linear progressions. In the meantime, give it a try, I'm sure you will love it.

I couldn't post a webstart demo of the small effect I presented here but I'll try to do that this week if I have some time. Yet, you can download the animation (QuickTime format, 46kb) to see what it looks like. You can also check the talk Desktop Java in Action Richard Bair and I gave at JavaPolis. At the end of the presentation, in the FX section, I talk more about how to use Swing's timers for animations and the related problems.

Related Topics >>