How to use glass pane for animation (SwingX and Timingframework)

Posted by

Table of contents
Introduction
Background information
Getting started
Implementation details
#1 Implementation of the event listener that will trigger the animator to start
#2 implementation of the custom component that performs the animation
Extras
Summary
Related links

Introduction

I needed to perform animations in the app that I’m building (http://screamingtoaster.com). I needed to build animations that move various components around on the screen, and other animations that pop up components on top of existing components, etc. After creating a few of these effects, I realized that I was doing the same thing over and over again, which is why I decided to write this tutorial to encapsulate this pattern, in the hopes that I will help others doing the same thing.

The strategy outlined here is just a suggestion on how to perform animation using a glass pane. There are many more strategies that you can select, depending on your requirements. However, I feel that this is one of the simplest and most effective approaches that have worked well for me, and I use it all the time in creating the user interface for applications in my platform product – http://screamingtoaster.com. If you look at some of the animations on the video posted on this website, they all use this strategy.

The following is a list of approaches that you can take to create custom components:

Strategy Cost
Create your own components that implement paint() methods, or override the paint() method on existing components Medium/High level of effort, since you have to generate your own paint method
Use SwingX API to install your own custom painters Low level of effort, since you can mix and match a variety of painters that are provided for you. It’s easy to write painters as well
Use the glass pane to perform custom painting on top of your existing components Low level of effort, since you can just paint what you need, instead of re-doing painting code for various components
Implement your own look and feel High level of effort, unless you just tweak with a few UIDelegates

The following is a list of approaches that you can take to create animations:

Strategy Cost
Create your animations in a custom paint() method Medium/High level of effort, since you may have to implement the whole thing from scratch. You can speed things up (reduce your coding effort) by drawing over existing components using super.paint()
Use the glass pane to draw the animation sequences on Low/Medium level of effort, since you may just have to paint on top of existing components
Implement your own look and feel High level of effort

The easiest approach is to performing animations is using the glass pane. This tutorial will outline a strategy to using the glass pane for performing your own custom animation on components, to add special effects like Spring and Glow, and to even place custom components on top of existing components to show error conditions and such, and have components fly down and move up.

Background information

For more information on glasspane and RootPaneContainers refer to these links:

  1. The Java Tutorial – How to use Root Panes
  2. Java 6 javadocs on JRootPane

For more information on SwingX, refer to these links:

  1. SwingLabs – SwingX toolkit
  2. Download SwingX here – Hudson Weekly Builds

I’m planning on writing more tutorials on SwingX and how to use painters… check back in a few days/weeks and I should have them uploaded on the site.

For more information on the Timingframework for animation, refer to these lniks:

  1. Timingframework tutorial – Timing is Everything
  2. Timingframework homepage – timingframework.java.net
  3. Timingframework downloads – java.net downloads

Getting started

The basic idea is to have all animation effects delegated to a component that’s placed on the glass pane, on top of your container (dialog, frame, etc). When the appropriate event triggers the animation to being, the custom component has to be installed as the glass pane of your RootPaneContainer and then your animator can be started which runs the animation, and when it’s complete, the glass pane has to be unistalled. The following diagram depicts this sequence of events:

figure1

Implementation details

You have to implement 2 things:

  1. The event listener that will trigger the animator to start – and the implementation of Animator’s TimingTarget interface. This event listener will be bound to some event source in your container/RootPaneContainer. You can create and initialize your custom component here.
  2. The custom component that will be installed on the RootPaneContainer’s glass pane. This component will be created and initialized by your container/RootPaneContainer.

#1 implementation of the event listener that will trigger the animator to start

In your event listener, be sure to perform the following in your TimingTarget implementation:

  1. Create the Animator and it’s TimingTarget object.
  2. In the TimingTarget implementation, do the following:
    1. begin() – create your custom component, initialize it, and install it on the RootPaneContainer
    2. timingEvent(float) – delegate to your custom component (which will cause a repaint)
    3. end() – uninstall the custom component from the RootPaneContainer.
  3. Start the Animator after you’ve configured it with the appropriate Interpolator, timing resolution, etc.

Here’s is some sample code below of an Animator that you could create in an event handler in your container:

   1: RootPaneContainer container = ...;
   2: AnimCompGlassPane component = new AnimCompGlassPane();
   3: component.init(...);
   4: component.installOnContainer(container);
   5:
   6: Animator animator = new Animator(
   7:   ANIMATION_LENGTH, ANIMATION_REPEATS, Animator.RepeatBehavior.REVERSE,
   8:   new TimingTargetAdapter() {
   9:     @Override public void begin() {
  10:     component.startAnimation();
  11:   }
  12:
  13:
  14:
  15: @Override public void timingEvent(float v) {
  16:     component.setAnimationPhase(v);
  17:   }
  18:
  19:   @Override public void end() {
  20:     component.endAnimation();
  21:     component.uninstallFromContainer();
  22:   }
  23:   });
  24:
  25: animator.setInterpolator(new SplineInterpolator(0.0f, 0.99f, 0.01f, 1.0f));
  26: animator.setResolution(ANIMATION_RESOLUTION);
  27: animator.start();

#2 implementation of the custom component that performs the animation

In your custom component, that actually performs the painting for the animation, be sure to perform the following:

  1. Provide an install(RootPaneContainer) method that installs this component on the glass pane of the RootPaneContainer after saving off the old component that’s in the glass pane. There is only one glass pane per container, so be sure to save off an existing one and restore it once your animation is complete – so be careful not to clobber existing glass panes.
  2. Provide an uninstall() method that removes this component from the RootPaneContainer’s glass pane and restores the original glass pane component.
  3. Provide a method that will be called by timingEvent(float) of the TimingTarget implementation. This method should store the current animation stage (which is a float between 0f and 1f that signifies how much of the animation is complete), and it should call repaint().
  4. Provide an implementation of paint(Graphics) method that performs your custom painting code. It should use the float value received in the method above to determine what stage of the animation to paint.

Here is some sample code, that may be a partial implementation of your animated component that goes on the glass pane:

   1: private RootPaneContainer _rootPaneContainer;
   2: private Component _oldGlasspane;
   3: private float _animPhase;
   4:
   5: public void installOnContainer(RootPaneContainer container){
   6:   _rootPaneContainer = container;
   7:   _oldGlasspane = _rootPaneContainer.getGlassPane();
   8:   _rootPaneContainer.setGlassPane(this);
   9:   setVisible(true);
  10:   requestFocusInWindow();
  11: }
  12:
  13: public void uninstallFromContainer(){
  14:   setVisible(false);
  15:   _rootPaneContainer.setGlassPane(_oldGlasspane);
  16:   _rootPaneContainer = null;
  17:   _oldGlasspane = null;
  18: }
  19:
  20: public void startAnimation(){...}
  21:
  22: public void setAnimationPhase(float v){
  23:   _animPhase = v;
  24:   repaint();
  25: }
  26:
  27: public void endAnimation(...);
  28:
  29: public void paint(Graphics g){
  30:   super.paint(g);
  31:   // do something with _animPhase and paint... 
  32: }

Extras

When the glass pane is installed on your RootPaneContainer, you should request that it gets keyboard focus. This way, you won’t see any strange artifacts of keypresses moving things around underneath the component in the glasspane. You can also install a sink that absorbs all key press and mouse events so that these aren’t passed on to components other that what’s on your glass pane. You can also choose to respond to mouse events or key presses by removing the glass pane (kick off the animation which hides the component in the glass pane for example). The same pattern used in the #1 would apply here!

Summary

The strategy outlined here is just a suggestion on how to perform animation using a glass pane. There are many more strategies that you can select, depending on your requirements. However, I feel that this is one of the simplest and most effective approaches that have worked well for me, and I use it all the time in creating the user interface for applications in my platform product – http://screamingtoaster.com. If you look at some of the animations on the video posted on this website, they all use this strategy.

Related links