7.3 A Very Simple Animator

 < Day Day Up > 



7.3 A Very Simple Animator

Animation is a technique where pictures are changed slightly and displayed on a terminal in succession so quickly that the eye perceives movement. It is a nice way to explain a number of topics; the results of running a program are visual, so problems in a program are readily apparent. Once the problem is seen, it is much easier to figure out why it occurred and how to correct it.

This chapter uses animation to illustrate how to implement the Java Event Model and provides a rationale for why it is implemented the way it is. A generic animator component is developed through a number of steps, each step illustrating and solving a problem in the previous animator, until a final animator that implements a correct animation component is created. The authoritative description of the Java Event Model is given in the Java Beans Specification [SUN96] and is available from Sun's Web site as part of the Java documentation; the final animator component provided here follows the rules and conventions found in this specification. The reader should note that the Java Beans Specification shows that the Vector and Hashtable classes, while fully synchronized, are not thread safe. For that reason, it is probably better to use the Collection classes such as Lists and Maps that do not impose the penalty of synchronization on the methods. This topic is explored further in the problems at the end of the chapter.

To begin the discussion, a basic understanding of animation in general is needed. Computer animation is achieved by using a flip-card analogy. Children's game cards often have pictures on the back. These cards are numbered, and the pictures on adjacent cards vary slightly from each other. When the cards are placed in order, rapidly flipping from one card to the next gives the impression of motion. This process of flipping through pictures is also the way a movie works. Frames are displayed at a rate faster than the eye can process them. The pictures on these discrete frames changing slightly from one frame to the next gives the illusion of continuous motion.

The same effect can be accomplished with a computer. The computer simply generates pictures that are slightly different and displays them on the screen. If these images are displayed fast enough, they appear to have a continuous motion. Program7.1 (Exhibits 1 through 3) is an example of this effect. Here, a ball is moved back and forth from the top left of the screen to the bottom right. Because the ball is redrawn slightly as we move through each frame, it appears that the ball is moving across the screen.

Exhibit 1: Program7.1a: Path Class

start example

 import java.awt.Point; public interface Path {   /**    * Check to see if the path has MoreSteps.    */   public boolean hasMoreSteps();   /**    * Get the next position. If the path has no more steps,    * return the current position.    */   public Point nextPosition(); } 

end example

7.3.1 The Path Class

How Program7.1 (Exhibits 1 through 3) works can be explained by first examining how the program figures out where to draw an object using the Path interface. The Path interface is shown in Exhibit 1 (Program7.1a). A path is simply an ordered set of points indicating where an object will be drawn. The points are obtained by calling the hasMoreSteps and nextPosition methods. So, each time an object is to be drawn, the path is queried to obtain the next position to draw the object.

How the Path object determines how to get to the next position is not determined by the interface. Some real object needs to be implemented that will determine the actual behavior. Any object that defines this behavior and implements the Path interface can be used. Thus, the path could be a straight line, a spline curve, or a set of points stored in an array. The actual implementation of the path can be easily changed. In the programs in this directory, a simple straight line is used to generate the path, as shown in Exhibit 2 (Program7.1b). The Path object takes a starting position, an ending position, and a number of steps and by using a straight line calculates the positions at which to draw the object. An object to be animated must give the Path the points at which to start and end and the number of steps it should take to get from the starting point to the ending point. Then, the object can use the path in a loop by checking to see if there are more steps in the path and, if so, asking the path for the next position in the path.

Exhibit 2: Program7.1b: Straight Line Path Class

start example

 import java.awt.Point; public class StraightLinePath implements Path {   int startX, startY, endX, endY, steps;   int currentStep = -1;// This makes the first step 0   double deltaX, deltaY;   /**    *  Constructor stores the points and builds the information    *  needed to construct the next point. Note that for a path    *  we need the initial point for the path (X1, Y1), the final    *  point for the path (X2, Y2), and the number of steps in    *  the path (numSteps).    */   public StraightLinePath(int X1, int Y1, int X2, int Y2, int       numSteps) {     startX = X1;     startY = Y1;     endX = X2;     endY = Y2;     steps = numSteps;     deltaX = ((double)(X2 - X1))/steps;     deltaY = ((double)(Y2 - Y1))/steps;   }   /**    *Check to see if the path has MoreSteps.    */   public boolean hasMoreSteps() {     if (currentStep > steps)       return false;     return true;   }   /**    *Get the next position. If the path has no more steps,    *return the current position.    */   public Point nextPosition() {     currentStep++;     if (currentStep > steps)       return new Point(endX, endY);     return new Point((int)(startX + (deltaX * currentStep)),         (int)(startY + (deltaY * currentStep)));   } } 

end example

For example, to move a circle from a position of 10,10 to 100,100 in 10 steps, the program could do the following:

   Graphics g = new Graphics();   Path path = new StraightLinePath(10, 10, 100, 100, 10);   while (path.hasMoreSteps()) {     Point point = path.nextStep();     g.drawOval((int)pos.getX(), (int)pos.getY(), 15, 15);   } 

Using a path is something that will be done frequently in animation in order to calculate the positions of objects as they move on a screen. By creating a Path interface and the StraightLinePath class, this behavior has been abstracted and must be implemented only once. Now objects that need to move can create a Path object and allow that Path object to implement the logic behind calculating the points where the object should be drawn. This approach offers three advantages. First, the logic for implementing the path is not part of the paint method for the object to be animated; thus, the paint method is more cohesive, as it is responsible only for drawing the object and not for calculating its position which can make these objects significantly less complex, easier to understand, and easier to implement and debug. The second advantage is related to the first. Because the ability to calculate the position given a path is encapsulated inside of the Path class, the programmer for the object to be drawn does not need to worry about the details of how a path is calculated. This means that the algorithm to generate points in a path only needs to be developed, tested, and supported once, instead of in each and every object that requires a path.

The third advantage to using a path interface is that it also makes the object less tightly coupled to a particular behavior. For example, assume that at some point in the future the application no longer requires a straight line but must implement a Bessel function to calculate the positions for drawing the object. If the path was defined as part of the paint method in the animated object, the animated object would have to be changed in order to implement this new behavior; thus, the behavior of the path is tightly coupled with this object. However, by using a path interface, the definition for the path can be changed by changing the type of path object that is used. Rather than making the path a StraightLinePath, a BesselPath object could be created, and only the actual object instantiated would have to be changed. This decoupling of the animated object from the specific path it will use allows greater flexibility in implementing new ways to animate the object.

7.3.2 A Simple Animator

Now that we have examined the method of calculating where to draw an object, we will now take a look at the way that the Java AWT and Java Swing handle drawing by using a simple animator with a ball, as shown in Exhibit 3 (Program7.1c). This animator consists of two parts: a SimpleAnimator class, which is also a JFrame because it extends JFrame, and a Ball class, which is also a JPanel because it extends JPanel. The discussion begins by describing the JFrame and then covers how it works with the JPanel.

Exhibit 3: Program7.1c: Simple Animator and Ball Classes

start example

 import java.awt.*; import javax.swing.*; public class SimpleAnimator extends JFrame {   Ball ball = new Ball(Color.red, 20, 20, 450, 450);   /**    *  Constructor that adds the ball and then sets the size    *  of the frame and shows it on the screen.    */   public SimpleAnimator() {     Container con = this.getContentPane();     con.add(ball);   }   /**    *  This method simply dispatches paint events to the ball    *  object, waits for 0.1 seconds, and does it again.    */   public void paint(Graphics g) {     super.paint(g);     try {       Thread.sleep(100);       repaint();     } catch(InterruptedException e) {     }   }   /**    *Start the program.    */   public static void main(String args[]) {     SimpleAnimator sa = new SimpleAnimator();     sa.setSize(500,500);     sa.show();   } } /**  *  Purpose: This class implements a ball JPanel on which  *           it can draw a ball to be displayed on the JFrame to  *           which it has been added. It animates the ball by moving  *           it on the screen a little each time the paint method  *           is called.  */ class Ball extends JPanel {   Color ballColor;   int xStart, yStart, xLimit, yLimit;   Path myPath;   /**    *  Constructor sets the ball up to run and sets the limits    *  for the size of the screen area on which it is to run.    *  It also sets the initial path by which to move the ball.    */   public Ball(Color ballColor, int xStart, int yStart,       int xLimit, int yLimit) {     this.ballColor = ballColor;     this.xStart = xStart;     this.yStart = yStart;     this.xLimit = xLimit;     this.yLimit = yLimit;     myPath = new StraightLinePath(xStart, yStart, xLimit,       yLimit, 50);   }   /**    *  This method draws the ball at the correct position on    *  the screen given by the path. When the end of path is    *  reached, a new path is created to move the ball in the    *  opposite direction.    */   public void paint(Graphics g) {     super.paint(g);     Point pos = myPath.nextPosition();     g.setColor(ballColor);     g.fillOval((int)pos.getX(), (int)pos.getY(), 15, 15);     if (! myPath.hasMoreSteps()) {       if (pos.getX() = = xStart)         myPath = new StraightLinePath(xStart, yStart, xLimit,           yLimit, 50);       else         myPath = new StraightLinePath(xLimit, yLimit, xStart,           yStart, 50);     }   } } 

end example

A JFrame is simply a frame displayed on the computer screen that is responsible for displaying the graphics generated in paint methods and for sending events to any components added to it. For example, when a JButton is added to a JFrame object, the JFrame is responsible for displaying the button by calling the paint method in the button. It is also responsible for making sure that any time the mouse is clicked when the cursor is inside of the button that the button is informed so that it can call the actionPerformed method for any ActionListeners registered with that button (this topic should become clear by the end of this chapter). In this section, the only events that the JFrame will handle are messages to "repaint" the screen, or repaint events.

When a repaint event is received by the JFrame, its paint method and the paint methods of all of the components added to it are called. Repaint events are generated when the Frame needs to be redrawn or redisplayed - for example, when the application first comes up and needs to be drawn or when the screen is resized. A programmer can also force a repaint event by calling the repaint method on the JFrame object. This is what is done in the paint method in the SimpleAnimator class. The paint method for the SimpleAnimator first executes any code, sleeps for 0.1 seconds, and then calls repaint, which schedules the JFrame to call paint again. The paint method then exits, and the JFrame processes the repaint event, calling the paint method again. This creates a JFrame object that is redrawn every 0.1 seconds and so can be used to implement an animation where the pictures are redrawn every 0.1 seconds.

We now turn our attention to the behavior of the JPanel object, the Ball object. As discussed earlier, a JFrame not only draws itself on a screen, but it also extends a Container, so it can have any number of Component [1] objects added to it. When the repaint event is generated for a JFrame, the paint methods for all of the objects that have been added to this JFrame are also called. Because class Ball extends JPanel, which in turn extends Component, this Ball object can be added to the JFrame and have its paint method called every 0.1 seconds. The default paint method for the JPanel is then redefined for class Ball, which allows it to draw a filled oval, or ball, on the JFrame on the computer screen. Because it checks with the path variable each time it is drawn, it moves the ball a little every 0.1 seconds, which produces the appearance of a ball moving across the screen. When it reaches the end of a path, it simply creates a new path to reverse direction, so the ball moves back and forth across the screen.

This SimpleAnimator is effective, but to make it more usable it would be nice to add a ScrollBar to control the speed of the animation (which we will do in the next section). Before starting to add control components, an important aspect of this animator should be discussed. Although it might appear otherwise, the repaint call in the SimpleAnimator does not call the paint method. If the repaint were to call the paint method, it would represent a recursive call, and the mechanism would eventually cause the program stack to overflow. This stack overflow would occur fairly quickly, so the design of the Java AWT and Swing components would be very unstable. Instead, the GUI thread that was talked about in Chapter 1 is started when a JFrame is started. This GUI thread runs as long as the JFrame is displayed and is responsible for calling the paint method in the JFrame object (in this case, the SimpleAnimator object) each time a repaint event is scheduled. So, when repaint is called from the paint method, a flag is set for the GUI thread that tells the GUI thread to call paint again. The GUI thread then exits the paint method, finds this flag set, and again calls paint. This distinction between repaint calling paint and setting a repaint event is very significant, and as shown in the next section. If it is not understood, it can cause an application to be implemented in such a way as to perform very badly or possibly not work at all.

[1]The term Component here specifically means the class "java.awt.Component," not generic components. This is the only place in the book that uses the term to mean this specific class.



 < Day Day Up > 



Creating Components. Object Oriented, Concurrent, and Distributed Computing in Java
The .NET Developers Guide to Directory Services Programming
ISBN: 849314992
EAN: 2147483647
Year: 2003
Pages: 162

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net