Open source Java projects: AnimatingCardLayout

Animated transitions for your Java GUIs

1 2 3 Page 3
Page 3 of 3

On with the show

Now that you understand how SlideShow works, let's try out this application. At the command line, invoke the following command to compile SlideShow.java:

javac -cp animatingcardlayout.jar SlideShow.java

This command assumes that animatingcardlayout.jar and SlideShow.java are located in the same directory.

Following a successful compilation, invoke the command below to run the application.

java -cp animatingcardlayout.jar;. SlideShow \windows

Note that this command assumes a Windows XP platform, and that the \windows directory contains at least two GIF and/or JPEG files.

As with AnimatingCardLayoutTest, you might notice some flicker during SlideShow's transition effects. I discuss this problem and present a solution in the next section.

Roll your own transition effects

Although AnimatingCardLayout's six transition effects are sufficient for many applications, you might want to implement your own effects. The first thing you need to know about creating a new transition effect class is that this class must implement AnimatingCardLayout's Animation interface, in terms of these three methods:

  • public Component animate(Component first, Component last, AnimationListener listener) returns a component on which a sequence of images (ranging from an image of first to an image of last, or vice-versa) are rendered by an internal thread. When the animation ends, the thread notifies the layout manager via listener.
  • public void setAnimationDuration(int duration) establishes the length of the animation, in milliseconds. The argument passed to setAnimationDuration() is the maximum of the duration passed to AnimatingCardLayout's setAnimationDuration() method and 500.
  • public void setDirection(boolean direction) sets the direction of the animation sequence. By convention, the animation sequence ranges from first to last (previously passed to animate()) when the direction argument is true, and vice-versa when this argument is false.

You also need to understand the context in which the aforementioned methods are used. In other words, you need to understand how they interact with AnimatingCardLayout. The following steps outline this interaction from the moment that show() is called until the moment that the layout manager receives notification about the animation finishing:

  1. AnimatingCardLayout's show() method, which is the entry point into working with the layout manager, invokes a private animate() method to perform setup tasks and indirectly begin the animation. One of this private method's arguments determines the direction in which animation proceeds (current component to the show()-specified component, or vice-versa).
  2. The private animate() method invokes Animation's setDirection() method to set the direction. By convention, true means animate from the current component to the show()-specified component; false means animate from the show()-specified component to the current component.
  3. The private animate() method invokes Animation's setAnimationDuration() method to establish the length of the animation, followed by this interface's animate() method to animate a sequence of images and notify the layout manager when the animation finishes.
  4. Animation's animate() method creates and returns a component (typically an instance of a JPanel subclass), passing its arguments to this component class's constructor. In turn, the constructor takes snapshots of the first and last components, and installs a thread (or a timer) that animates from the first/last snapshot image to the last/first snapshot image.
  5. When the thread/timer finishes the animation, it notifies AnimatingCardLayout to perform cleanup by invoking listener's public void animationFinished() method -- given that AnimatingCardLayout implements the AnimationListener interface. This method must be invoked on the event-dispatching thread.

I've created a ZoomAnimation class that renders a zoom transition similar to the zoom transition found in Apple's iPhone product. After the current card's component shrinks to the upper-left corner of the containing window, the next card's component grows in size from this corner until it fills the window. Listing 2 presents ZoomAnimation's source code.

Naming convention
In keeping with the naming convention established by the six example transition effect classes, ZoomAnimation includes Animation in its name.

Listing 2. ZoomAnimation.java

// ZoomAnimation.java

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;

import javax.swing.*;

import org.javadev.effects.*;

public class ZoomAnimation implements Animation
{
   boolean direction = true;
   int animationDuration = 2000;
   SpecialPanel animationPanel;

   public Component animate (Component first, Component last,
                             AnimationListener listener)
   {
      return new SpecialPanel (first, last, listener);

/*
      For many effects, you would employ direction logic such as that shown
      below -- see CubeAnimation.java, DashboardAnimation.java, and
      SlideAnimation.java for examples. This logic does not work for
      ZoomAnimation because SlideShow keeps alternating between a pair of
      components, which causes AnimatingCardLayout to keep switching
      direction. To see the result, comment out the return statement above,
      and uncomment the return statement below.
*/

/*
      return new SpecialPanel ((direction) ? first : last,
                               (direction) ? last : first, listener);
*/
   }

   public void setAnimationDuration (int duration)
   {
      animationDuration = duration;
   }

   public void setDirection (boolean direction)
   {
      this.direction = direction;
   }

   class SpecialPanel extends JPanel
   {
      final static int STEP_TIME = 50;

      BufferedImage firstImage, secondImage;
      double incr, xscale, yscale;
      int maxsteps, step;
      Timer timer;

      SpecialPanel (Component component1, Component component2,
                    final AnimationListener listener)
      {
         // Take a snapshot of the first component.

         firstImage = new BufferedImage (component1.getSize ().width,
                                         component1.getSize ().height,
                                         BufferedImage.TYPE_INT_RGB);
         Graphics g = firstImage.createGraphics ();
         component1.paint (g);
         g.dispose ();

         // Take a snapshot of the second component.

         secondImage = new BufferedImage (component2.getSize ().width,
                                          component2.getSize ().height,
                                          BufferedImage.TYPE_INT_RGB);
         g = secondImage.createGraphics ();
         component2.paint (g);
         g.dispose ();

         // Calculate the maximum number of steps in the animation sequence
         // and the scaling increment used to modify the x and y scale factors
         // during each step in the sequence.

         maxsteps = animationDuration/STEP_TIME;
         incr = 1.0/(maxsteps >> 1);

         // Create an action listener whose logic, which runs on the
         // event-dispatching thread, paints each step's image and terminates
         // the animation sequence after the last step has run.

         ActionListener al;
         al = new ActionListener ()
              {
                  public void actionPerformed (ActionEvent ae)
                  {
                     repaint ();

                     if (++step >= maxsteps)
                     {
                         timer.stop ();
                         listener.animationFinished ();
                     }
                  }
              };

         // Create and start a timer that invokes the action listener at a 
         // specific interval.

         timer = new Timer (STEP_TIME, al);
         timer.start ();
      }

      public void paint (Graphics g)
      {
         Graphics2D g2d = (Graphics2D) g;

         // Paint the background to remove any artifacts of previously
         // displayed image.

         g2d.setColor (Color.black);
         g2d.fillRect (0, 0, getWidth (), getHeight ());

         // Advance the animation sequence by calculating the next scale
         // factors and drawing either the first or the second image.

         if (step < (maxsteps >> 1)) // faster than maxsteps/2
         {
             if (step == 0)
             {
                 xscale = 1.0;
                 yscale = 1.0;
             }
             else
             {
                 xscale -= incr;
                 yscale -= incr;
             }
             g2d.scale (xscale, yscale);
             g2d.drawImage (firstImage, 0, 0, null);
         }
         else
         {
             if (step == (maxsteps >> 1))
             {
                 xscale = incr;
                 yscale = incr;
             }
             else
             {
                 xscale += incr;
                 yscale += incr;
             }
             g2d.scale (xscale, yscale);
             g2d.drawImage (secondImage, 0, 0, null);
         }
      }
   }
}

Listing 2 should be fairly easy to understand. Instead of following the pattern laid out by the six example transition effect classes, I simplified ZoomAnimation.java because I found the code for these classes to be somewhat hard to follow. Furthermore, I wanted to prevent the flickering problem that I encountered on my platform when viewing the example transition effects.

The flicker problem in AnimatingCardLayout's six given transition effects appears to result from a race condition between the animating and event-dispatching threads. If firstImage and secondImage are set to null before the paint() method is invoked, the window is momentarily blanked. Changing invokeLater() to invokeAndWait() seems to fix this problem.

Double-buffering for older Java versions
Although not a problem under Java SE 6, ZoomAnimation's paint() method might also exhibit flicker under older versions of Java. If this is the case, you will need to employ double-buffering within this method to avoid this problem.

Expanding the slideshow

You will probably want to play with ZoomAnimation in the context of the SlideShow application. Complete the following steps to add this transition effect class to SlideShow:

  1. Copy ZoomAnimation.java to the same directory as SlideShow.java.
  2. Insert a new ZoomAnimation () entry into SlideShow's animations array.
  3. Compile the source files via javac -cp animatingcardlayout.jar;. SlideShow.java.

After running SlideShow (as described earlier) and seeing the zoom transition effect in action, you can make this effect more dramatic by commenting out the background-painting code in ZoomAnimation's paint() method.

In conclusion

AnimatingCardLayout is an interesting open source Java project for integrating animated transitions into Java GUIs. One downside of the project is its lack of API documentation, which means you must review the source code to learn about the API. I also found the source code for the layout manager's transition-effect class somewhat hard to follow. Despite these drawbacks, I like this layout manager and enjoyed extending it with the ZoomAnimation class. I think you'll like it, too.

Jeff Friesen is a freelance software developer and educator who specializes in Java technology. Check out his javajeff.mb.ca Website to discover all of his published Java articles and more.

Learn more about this topic

1 2 3 Page 3
Page 3 of 3