Animating a Series of Images
The next example demonstrates animating a series of images that are stored in an array of ImageIcons. The animation presented in Fig. 21.2Fig. 21.3 is implemented using a subclass of JPanel called LogoAnimatorJPanel (Fig. 21.2) that can be attached to an application window or a JApplet. Class LogoAnimator (Fig. 21.3) declares a main method (lines 820 of Fig. 21.3) to execute the animation as an application. Method main declares an instance of class JFrame and attaches a LogoAnimatorJPasnel object to the JFrame to display the animation.
Figure 21.2. Animating a series of images.
(This item is displayed on pages 983 - 984 in the print version)
1 // Fig. 21.2: LogoAnimatorJPanel.java 2 // Animation of a series of images. 3 import java.awt.Dimension; 4 import java.awt.event.ActionEvent; 5 import java.awt.event.ActionListener; 6 import java.awt.Graphics; 7 import javax.swing.ImageIcon; 8 import javax.swing.JPanel; 9 import javax.swing.Timer; 10 11 public class LogoAnimatorJPanel extends JPanel 12 { 13 private final static String IMAGE_NAME = "deitel"; // base image name 14 protected ImageIcon images[]; // array of images 15 private final int TOTAL_IMAGES = 30; // number of images 16 private int currentImage = 0; // current image index 17 private final int ANIMATION_DELAY = 50; // millisecond delay 18 private int width; // image width 19 private int height; // image height 20 21 private Timer animationTimer; // Timer drives animation 22 23 // constructor initializes LogoAnimatorJPanel by loading images 24 public LogoAnimatorJPanel() 25 { 26 images = new ImageIcon[ TOTAL_IMAGES ]; 27 28 // load 30 images 29 for ( int count = 0; count < images.length; count++ ) 30 images[ count ] = new ImageIcon( getClass().getResource( 31 "images/" + IMAGE_NAME + count + ".gif" ) ); 32 33 // this example assumes all images have the same width and height 34 width = images[ 0 ].getIconWidth(); // get icon width 35 height = images[ 0 ].getIconHeight(); // get icon height 36 } // end LogoAnimatorJPanel constructor 37 38 // display current image 39 public void paintComponent( Graphics g ) 40 { 41 super.paintComponent( g ); // call superclass paintComponent 42 43 images[ currentImage ].paintIcon( this, g, 0, 0 ); 44 45 // set next image to be drawn only if timer is running 46 if ( animationTimer.isRunning() ) 47 currentImage = ( currentImage + 1 ) % TOTAL_IMAGES; 48 } // end method paintComponent 49 50 // start animation, or restart if window is redisplayed 51 public void startAnimation() 52 { 53 if ( animationTimer == null ) 54 { 55 currentImage = 0; // display first image 56 57 // create timer 58 animationTimer = 59 new Timer( ANIMATION_DELAY, new TimerHandler() ); 60 61 animationTimer.start(); // start timer 62 } // end if 63 else // animationTimer already exists, restart animation 64 { 65 if ( ! animationTimer.isRunning() ) 66 animationTimer.restart(); 67 } // end else 68 } // end method startAnimation 69 70 // stop animation timer 71 public void stopAnimation() 72 { 73 animationTimer.stop(); 74 } // end method stopAnimation 75 76 // return minimum size of animation 77 public Dimension getMinimumSize() 78 { 79 return getPreferredSize(); 80 } // end method getMinimumSize 81 82 // return preferred size of animation 83 public Dimension getPreferredSize() 84 { 85 return new Dimension( width, height ); 86 } // end method getPreferredSize 87 88 // inner class to handle action events from Timer 89 private class TimerHandler implements ActionListener 90 { 91 // respond to Timer's event 92 public void actionPerformed( ActionEvent actionEvent ) 93 { 94 repaint(); // repaint animator 95 } // end method actionPerformed 96 } // end class TimerHandler 97 } // end class LogoAnimatorJPanel |
Figure 21.3. Displaying animated images on a JFrame.
(This item is displayed on page 985 in the print version)
1 // Fig. 21.3: LogoAnimator.java 2 // Animation of a series of images. 3 import javax.swing.JFrame; 4 5 public class LogoAnimator 6 { 7 // execute animation in a JFrame 8 public static void main( String args[] ) 9 { 10 LogoAnimatorJPanel animation = new LogoAnimatorJPanel(); 11 12 JFrame window = new JFrame( "Animator test" ); // set up window 13 window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); 14 window.add( animation ); // add panel to frame 15 16 window.pack(); // make window just large enough for its GUI 17 window.setVisible( true ); // display window 18 19 animation.startAnimation(); // begin animation 20 } // end main 21 } // end class LogoAnimator
|
Class LogoAnimatorJPanel (Fig. 21.2) maintains an array of ImageIcons that are loaded in the constructor (lines 2436). Lines 2931 create each ImageIcon object and store the animation's 30 images in array images. The constructor argument uses string concatenation to assemble the file name from the pieces "images/", IMAGE_NAME, count and ".gif". Each image in the animation is in a file called deitel# .gif, where # is a value in the range 029 specified by the loop's control variable count. Lines 3435 determine the width and height of the animation from the size of the first image in array imageswe assume that all the images have the same width and height.
After the LogoAnimatorJPanel constructor loads the images, method main of Fig. 21.3 sets up the window in which the animation will appear (lines 1217), and line 19 calls the LogoAnimatorJPanel's startAnimation method (declared at lines 5168 of Fig. 21.2). This method starts the program's animation for the first time or restarts the animation that the program stopped previously. [Note: This method is called when the program is first run, to begin the animation. Although we provide the functionality for this method to restart the animation if it has been stopped, the example does not call the method for this purpose. We have added the functionality, however, should the reader choose to add GUI components that enable the user to start and stop the animation.] For example, to make an animation "browser friendly" in an applet, the animation should stop when the user switches Web pages. If the user returns to the Web page with the animation, method startAnimation can be called to restart the animation. The animation is driven by an instance of class Timer (from package javax.swing). A Timer generates ActionEvents at a fixed interval in milliseconds (normally specified as an argument to the Timer's constructor) and notifies all its ActionListeners each time an ActionEvent occurs. Line 53 determines whether the Timer reference animationTimer is null. If it is, method startAnimation is being called for the first time, and a Timer needs to be created so that the animation can begin. Line 55 sets currentImage to 0, which indicates that the animation should begin with the image in the first element of array images. Lines 5859 assign a new Timer object to animationTimer. The Timer constructor receives two argumentsthe delay in milliseconds (ANIMATION_DELAY is 50, as specified in line 17) and the ActionListener that will respond to the Timer's ActionEvents. For the second argument, an object of class TimerHandler is created. This class, which implements ActionListener, is declared in lines 8996. Line 61 starts the Timer object. Once started, animationTimer will generate an ActionEvent every 50 milliseconds. Each time an ActionEvent is generated, the Timer's event handler actionPerformed (lines 9295) is called. Line 94 calls LogoAnimatorJPanel's repaint method to schedule a call to LogoAnimatorJPanel's paintComponent method (lines 3948). Remember that any subclass of JComponent that draws should do so in its paintComponent method. Recall from Chapter 11 that the first statement in any paintComponent method should be a call to the superclass's paintComponent method, to ensure that Swing components are displayed correctly.
If the animation was started earlier, then our Timer has been created and the condition in line 53 will evaluate to false. The program will continue with lines 6566, which restart the animation that the program stopped previously. The if condition at line 65 uses Timer method isRunning to determine whether the Timer is running (i.e., generating events). If it is not running, line 66 calls Timer method restart to indicate that the Timer should start generating events again. Once this occurs, method actionPerformed (the Timer's event handler) is again called at regular intervals. Each time, a call is made to method repaint (line 94), causing method paintComponent to be called and the next image to be displayed.
Line 43 paints the ImageIcon stored at element currentImage in the array. Lines 4647 determine whether the animationTimer is running and, if so, prepare for the next image to be displayed by incrementing currentImage by 1. The remainder calculation ensures that the value of currentImage is set to 0 (to repeat the animation sequence) when it is incremented past 29 (the last element index in the array). The if statement ensures that the same image will be displayed if paintComponent is called while the Timer is stopped. This could be useful if a GUI is provided that enables the user to start and stop the animation. For example, if the animation is stopped and the user covers it with another window, then uncovers it, method paintComponent will be called. In this case, we do not want the animation to show the next image (because the animation has been stopped). We simply want the window to display the same image until the animation is restarted.
Method stopAnimation (lines 7174) stops the animation by calling Timer method stop to indicate that the Timer should stop generating events. This prevents actionPerformed from calling repaint to initiate the painting of the next image in the array. [Note: Just as with restarting the animation, this example defines but does not use method stopAnimation. We have provided this method for demonstration purposes, or if the user wishes to modify this example so that it enables the user to stop and restart the animation.]
Software Engineering Observation 21.1
When creating an animation for use in an applet, provide a mechanism for disabling the animation when the user browses a new Web page different from the one on which the animation applet resides. |
Remember that by extending class JPanel, we are creating a new GUI component. Thus, we must ensure that our new component works like other components for layout purposes. Layout managers often use a GUI component's getPreferredSize method (inherited from class java.awt.Component) to determine the preferred width and height of the component when laying it out as part of a GUI. If a new component has a preferred width and height, it should override method getPreferredSize (lines 8386) to return that width and height as an object of class Dimension (package java.awt). The Dimension class represents the width and height of a GUI component. In this example, the images are 160 pixels wide and 80 pixels tall, so method getPreferredSize returns a Dimension object containing the numbers 160 and 80 (determined at lines 3435).
Look-and-Feel Observation 21.1
The default size of a JPanel object is 10 pixels wide and 10 pixels tall. |
Look-and-Feel Observation 21.2
When subclassing JPanel (or any other JComponent), override method getPreferredSize if the new component is to have a specific preferred width and height. |
Lines 7780 override method getMinimumSize. This method determines the minimum width and height of the component. As with method getPreferredSize, new components should override method getMinimumSize (also inherited from class Component). Method getMinimumSize simply calls getPreferredSize (a common programming practice) to indicate that the minimum size and preferred size are the same. Some layout managers ignore the dimensions specified by these methods. For example, a BorderLayout's NORTH and SOUTH regions use only the component's preferred height.
Look-and-Feel Observation 21.3
If a new GUI component has a minimum width and height (i.e., smaller dimensions would render the component ineffective on the display), override method getMinimumSize to return the minimum width and height as an instance of class Dimension. |
Look-and-Feel Observation 21.4
For many GUI components, method getMinimumSize is implemented to return the result of a call to the component's getPreferredSize method. |