Previous Page TOC Next Page



- 11 -
Simple Animation and Threads


One of the coolest things that VJ++ can do extremely well is complex animation. After all, VJ++ is a Java development tool and Java is primarily used for the Internet where presentation—graphics and animation—are paramount.

Try the following: using VJ++, create a new Project Workspace window by selecting New | Project Workspace from the menu. When the Wizards appear, select applet only, give it a name, and then click Create. On the next window, Step 1 of 5, select Finish. This will create a default project with animation and threads built in. Compile the project and view it using your browser. There it is, a fully multithreaded applet that does some pretty cool animation.

That sort of simple animation takes only a few methods to implement in Java, but these few methods are the basis for any Java applet that needs to update the screen dynamically. Starting with simple animation is a good way to build up to more complicated applets. Today, you'll learn the fundamentals of animation in VJ++: how the various parts of the system all work together so that you can create moving figures and dynamically updateable applets. Specifically, you'll explore the following:

Throughout today, you'll also work with lots of examples of real applets that create an animation or perform some sort of dynamic movement.

Creating Animation in VJ++


Animation in VJ++ involves two steps: constructing a frame of animation and then asking VJ++ to paint that frame, repeating as necessary to create the illusion of movement. The basic, static applets that you created yesterday taught you how to accomplish the first part; all that's left is how to tell Java to paint a frame.

Painting and Repainting


The paint() method, as you learned yesterday, is called by Java whenever the applet needs to be painted—when the applet is initially drawn, when the window containing it is moved, or when another window is moved over it. You can also, however, ask VJ++ to repaint the applet at a time you choose. So, to change the appearance of what is on the screen, you construct the image or frame you want to paint, and then ask VJ++ to paint this frame. If you do this repeatedly, and fast enough, you get animation inside your applet. That's all there is to it.

Where does all this take place? It doesn't take place in the paint() method itself. All paint() does is put dots on the screen. paint(), in other words, is responsible only for the current frame of the animation. The real work of changing what paint() does, of modifying the frame for an animation, actually occurs somewhere else in the definition of your applet.

In that "somewhere else," you construct the frame (set variables for paint() to use, create Color, Font, or other objects that paint() will need), and then call the repaint() method. repaint() is the function that causes VJ++ to call paint() and causes your frame to get drawn.



Because an applet can contain many different components that all need to be painted (as you'll learn later on this week), and, in fact, applets are embedded inside a larger Java application that also paints to the screen in similar ways, when you call repaint() (and therefore paint()) you're not actually immediately drawing to the screen as you do in other window or graphics toolkits. Instead, repaint() is a request for VJ++ to repaint your applet as soon as it can. Also, if too many repaint() requests are made in a short amount of time, the system might only call repaint() once for all of them. Much of the time, the delay between the call and the actual repaint is negligible.


Starting and Stopping an Applet's Execution


Remember start() and stop() from Day 9? These are the methods that trigger your applet to start and stop running. You didn't use start() and stop() yesterday because the applets on that day did nothing except paint once. With animation and other Java applets that are actually processing and running over time, you'll need to make use of start() and stop() to trigger the start of your applet's execution, and to stop it from running when you leave the page that contains that applet. For most applets, you'll want to override start and stop for just this reason.

The start() method triggers the execution of the applet. You can either do all the applet's work inside that method, or you can call other object's methods in order to do so. Usually, start() is used to create and begin execution of a thread, so that the applet can run in its own time.

stop(), on the other hand, suspends an applet's execution, so when you move off the page on which the applet is displaying, it doesn't keep running and using up system resources. Most of the time when you create a start() method, you should also create a corresponding stop().

Putting It Together


Explaining how to do VJ++ animation is more of a task than actually showing you how it works in code. An example or two will help make the relationship between all these methods clearer.

Listing 11.1 shows a sample applet that, at first glance, uses basic applet animation to display the date and time and constantly updates it every second, creating a very simple animated digital clock. A frame from that clock is shown in Figure 11.1.

The words "at first glance" in the previous paragraph are very important: this applet doesn't work! However, despite the fact that it doesn't work, you can still learn a lot about basic animation by examining it. So, working through the code will still be valuable. In the next section, you'll learn just what's wrong with it.

See if you can figure out what's going on with the code before you skip ahead to the analysis.

Listing 11.1. The date and time applet.




 1:  import java.awt.Graphics;



 2:  import java.awt.Font;



 3:  import java.util.Date;



 4:



 5:  public class DigitalClock extends java.applet.Applet



 6:  {



 7:      Font m_fntFont = new Font("TimesRoman", Font.BOLD, 24);



 8:      Date m_dteDate;



 9:



10:     public void start()



11:     {



12:         while (true)



13:         {



14:             m_dteDate = new Date();



15:             repaint();



16:             try



17:             {



18:                 Thread.sleep(1000);



19:             }



20              catch (InterruptedException e)



21:             {   }



22:          }



23:     }



24:     public void paint(Graphics g)



25:     {



26:         g.setFont(m_fntFont);



27:         g.drawString(m_dteDate.toString(), 10, 50);



28:     }



29: }

Figure 11.1. The digital clock.

Think you've got the basic idea? Now go through it, line by line.

Lines 7 and 8 define two basic data members: m_fntFont and m_dteDate, which hold objects representing the current font and the current date, respectively. More about these later.

The start() method triggers the actual execution of the applet. Note the while loop inside this method; given that the test (true) always returns true, the loop never exits. A single animation frame is constructed inside that while loop with the following steps:

On to the paint() method. Here, inside paint(), all that happens is the current font (in the variable m_fntFont) is set, and the date is printed to the screen. You have to call the toString() method to convert the date to a string. Because paint() is called repeatedly with whatever value happens to be in m_dteDate, the string is updated every second to reflect the new date.



the toString() method is one of the most common VJ++ functions. If you run a Query on toString() (select Help | Search from the menu) you will get somewhere in the range of 100 topics found. This basically means that toString() is used to convert almost any non-string variable to a usable string.

There are a few things to note about this example. First, you might think it would be easier to create the new Date object inside the paint() method. That way you could use a local variable and not need a data member to pass the Date object around. Although doing things that way creates cleaner code, it also results in a less efficient program. The paint() method is called every time a frame needs to be changed. In this case, it's not that important, but in an animation that needs to change frames very quickly, the paint() method has to pause to create that new object every time. By leaving paint() to do what it does best—painting the screen—and calculating new objects beforehand, you can make painting as efficient as possible. This is precisely the same reason why the Font object is also in a data member.

Threads—What They Are and Why You Need Them


Depending on your experience with operating systems and with environments within those systems, you might or might not have run into the concept of threads. Start from the beginning with some definitions.

When a simple program runs, it starts executing, runs its initialization code, calls methods or procedures, and continues running and processing until its tasks are complete and the program terminates itself or is exited by a user. This type of program uses a single thread, where the thread is a single focus of control for the program.

Multithreading, as in Java, enables several different execution threads to run at the same time inside the same program, in parallel, without interfering with each other.

Here's a simple example: suppose you have a long computation near the start of a program's execution. This long computation might not be needed until later in the program's execution. It's actually tangential to the main point of the program; it needs to get finished eventually, but not right away. In a single-threaded program, you have to wait for that computation to finish before the rest of the program can continue running. In a multithreaded system, you can put that computation into its own thread, enabling the rest of the program to continue running independently. Then, at some later point during the execution, the threads meet (for example, the computation stores a value in a variable where a comparison operation checks for a value) and the program continues on.

Now look at a real-world example, such as your car. If you consider your car to be a multithreaded program, you end up with a series of statements: when the car program is started, several threads are started—the engine thread, the radio thread, the air recirculating fan thread, and so on. Each of these threads basically runs independently of each other. If the engine thread stops, the radio and air recirculating fan threads won't necessarily stop (at least until the battery goes dead).

Using threads in Java, you can create an applet that runs in its own thread and will happily run all by itself without interfering with any other part of the system. Using threads, you can have lots of applets running at once on the same page. Be careful, though. Depending on how many threads you start, you might eventually tax the system so that all of them will run slower, yet they all will still run independently.

Even if you don't have the requirement to have lots of applets on a single page, using threads in your applets is good Java programming practice. The general rule of thumb for well-behaved applets is that whenever you have any bit of processing that is likely to continue for a long time (such as an animation loop, or a bit of code that takes a long time to execute), put it in its own thread.

The Problem with the Digital Clock Applet


The digital clock applet in the last section doesn't use threads. Instead, you put the while loop that cycles through the animation directly into the start() method so that when the applet starts running it keeps going until you quit the browser or applet viewer. Although this might seem like a good way to approach the problem, the digital clock won't work because the while loop in the start() method is monopolizing all the resources in the system, including painting. If you try compiling and running the digital clock applet, all you get is a blank screen. You also won't be able to stop the applet normally, because there's no way a stop() method can ever be called.

The solution to this problem is to rewrite the applet using threads. Threads enable this applet to animate on its own without interfering with other system operations, to be started and stopped (when you enter and exit the Web page), and to run it in parallel with other applets.

Writing Applets with Threads


How do you create an applet that uses threads? There are several things you need to do. Fortunately, none of them are difficult, and a lot of the basics of using threads in applets is just boilerplate code that you can copy and paste from one applet to another. Because it's so easy, there's almost no reason not to use threads in your applets.

There are four modifications you need to make to create an applet that uses threads:

  1. Change the signature of your applet class to include the words implements Runnable.
  2. Include a data member to hold this applet's thread.
  3. Modify your start() method to do nothing but spawn a thread and start it running.
  4. Create a run() method that contains the actual code that starts your applet running.

The first change is to the first line of your class definition. You've already got something like this:




public class MyAppletClass extends java.applet.Applet



{



...



}

You need to change it to the following:




public class MyAppletClass extends java.applet.Applet implements Runnable



{



...



}

When you include the Runnable keyword in your class definition, you'll have support for the Runnable interface in your applet. If you think way back to Day 4, you'll remember that interfaces are a way to collect method names common to different classes, which can then be implemented inside different classes that need to implement that behavior. Here, the Runnable interface defines the behavior your applet needs to run a thread; in particular, it gives you a default definition for the run() method. By implementing Runnable you tell other objects (in particular, a Browser) that they can call the Run() method on your instances.

The second step is to add a data member to hold this applet's thread. Call it anything you like; it's a variable of the type Thread (Thread is a class in java.lang, so you don't have to import it):




Thread m_thRunner;

Third, add a start() method or modify the existing one so that it does nothing but create a new thread and start it running. Here's a typical example of a start() method:




public void start()



{



   if (m_thRunner == null)



   {



       m_thRunner = new Thread(this);



       m_thRunner.start();



   }



}

If you modify start() to do nothing but spawn a thread, where does the body of your applet go? It goes into a new method, run(), which looks like this:




public void run()



{



    // what your Applet actually does



}

run() can contain anything you want your class' thread to do: initialization code, the actual loop for your applet, or anything else that needs to run in its own thread. You also can create new objects and call methods from inside run(), and they'll run inside that thread. The run() method is the real heart of your applet.

Finally, now that you've got threads running and a start method to start them, you should add a stop() method to suspend execution of that thread (and, therefore, whatever the applet is doing at the time) when the browser leaves the page. stop(), like start(), is usually something along these lines:




public void stop()



{



  if (m_thRunner != null)



  {



      m_thRunner.stop();



      m_thRunner = null;



  }



}

The stop() method here does two things: it stops the thread from executing and also sets the thread's variable runner to null. Setting the variable to null makes the Thread object it previously contained available for garbage collection so that the applet can be removed from memory after a certain amount of time. If the reader comes back to this page and this applet, the start method creates a new thread and starts up the applet once again.



All this stuff about start, stop, and creating threads can be confusing. The purpose of creating and destroying (setting the thread object to null) is to minimize the amount of memory that your applet takes when it's not the applet that's visible to the user. Even though you destroy and create new threads in the stop and start methods, you do not lose the values in your data members. Because you do not lose the data in your variables, you are not reinitializing your applet, just sort of pausing its execution.

And that's it! With four basic modifications, you can create a well-behaved applet that runs in its own thread.

Fixing the Digital Clock


Remember the problems you had with the digital clock applet at the beginning of this section? You'll now fix those problems so you can get an idea of how a real applet with threads looks. You'll follow the four steps outlined in the previous section.

First, modify the class definition to include the Runnable interface (the class is renamed to DigitalThreads instead of DigitalClock):




public class DigitalThreads extends java.applet.Applet implements Runnable



{



    ...



}

Second, add a data member for the Thread:




Thread m_thRunner;

For the third step, swap the way you did things. Because the bulk of the applet is currently in a method called start(), but you want it to be in a method called run(), rather than do a lot of copying and pasting, just rename the existing start() to run():




public void run()



{



  ...




}

Finally, add the boilerplate start() and stop() methods:




public void start()



{



   if (m_thRunner == null)



   {



       m_thRunner = new Thread(this);



       m_thRunner.start();



   }



}



public void stop()



{



  if (m_thRunner != null)



  {



      m_thRunner.stop();



      m_thRunner = null;



  }



}

You're finished! One applet converted to use threads in less than a minute flat. (Of course, this depends upon typing speed. Your actual time might vary—taxes and license not included.) The code for the final applet appears in Listing 11.2.

Listing 11.2. The fixed digital clock applet.




 1:  import java.awt.Graphics;



 2:  import java.awt.Font;



 3:  import java.util.Date;



 4:



 5:  public class DigitalClock extends java.applet.Applet



 6:  {



 7:      Font m_fntFont = new Font("TimesRoman", Font.BOLD, 24);



 8:      Date m_dteDate;



 9:



10:     public void run()



11:     {



12:         while (true)



13:         {



14:             m_dteDate = new Date();



15:             repaint();



16:             try



17:             {



18:                 Thread.sleep(1000);



19:             }



20              catch (InterruptedException e)



21:             {   }



22:         }



23:     }



24:     public void paint(Graphics g)



25:     {



26:         g.setFont(m_fntFont);



27:         g.drawString(m_dteDate.toString(), 10, 50);



28:     }



29:     public void start()



30:     {



31:         if (m_thRunner == null)



32:         {



33:             m_thRunner = new Thread(this);



34:             m_thRunner.start();



35:         }



36:     }



37:     public void stop()



38:     {



39:         if (m_thRunner != null)



40:         {



41:             m_thRunner.stop();



42:             m_thRunner = null;



43:         }



44:     }



45: }

Reducing Animation Flicker


If you've been following along with this book and trying the examples as you go, rather than reading this book on an airplane, in the bathtub, or in another favorite reading place, you might have noticed that when the date program runs every once in a while, there's an annoying flicker in the animation. (Not that there's anything wrong with reading this book in the bathtub, but you won't see the flicker if you do that, so just trust me—there's a flicker.) This isn't a mistake or an error in the program; in fact, that flicker is a side effect of creating an animation in Java. Because it can be annoying, however, you'll learn how to reduce flicker in this part of today's lesson so that an animation runs cleaner and looks better on the screen.

Flicker and How to Avoid It


Flicker is caused by the way Java paints and repaints each frame of an applet. At the beginning of today's lesson, you learned that when you call the repaint() method, repaint() calls paint(). That's not precisely true. A call to paint() does indeed occur in response to a repaint(), but what actually happens are the following steps:

  1. The call to repaint() results in a call to the method update().
  2. The update() method clears the screen of any existing contents (in essence, fills it with the current background color), and then calls paint().
  3. The paint() method then draws the contents of the current frame.

It's Step 2, the call to update(), that causes animation flicker. Because the screen is cleared between frames, the parts of the screen that don't change alternate rapidly between being painted and being cleared. Hence, you have flickering.

There are two major ways to avoid flicker in your Java applets:

If the second way sounds complicated, that's because it is. Double-buffering involves drawing to an offscreen graphics surface and then copying that entire surface to the screen. Because it's more complicated, you'll explore that one tomorrow. Today, we'll cover the easier solution: overriding update().

How to Override Update


The cause of flickering lies in the update() method. To reduce flickering, therefore, override update(). Here's what the default version of update() does (in the Component class, which you'll learn more about on Day 13):




public void update(Graphics g)



{



    g.setColor(getBackground());



    g.fillRect(0, 0, width, height);



    g.setColor(getForeground());



    paint;



}

Basically, update() clears the screen (or, to be exact, fills the applet's bounding rectangle with the background color), sets things back to normal, and then calls paint(). When you override update(), you have to keep these two things in mind and make sure that your version of update() does something similar. In the next two sections, you'll work through some examples of overriding update() in different ways to reduce flicker.

Solution One—Don't Clear the Screen


The first solution to reducing flicker is not to clear the screen at all. This works only for some applets. If your applet must redraw to the same area of the screen each time, this solution is for you. If your applet has a graphic that changes shape and/or moves around the screen, this solution is not the one to use.

The following is an example of an applet that flickers a lot. The ColorSwirl applet prints a single string to the screen ("All the swirly colors"), but that string is presented in different colors that dynamically fade into each other. This applet flickers terribly when it's run. Listing 11.3 shows the source for this applet, and Figure 11.2 shows the result.

Listing 11.3. The ColorSwirl applet.




 1:  import java.awt.Graphics;



 2:  import java.awt.Color;



 3:  import java.awt.Font;



 4:



 5: public class ColorSwirl extends java.applet.Applet implements Runnable



 6: {



 7:    Font m_fntF = new Font("TimesRoman",Font.BOLD,48);



 8:    Color m_clrColors[] = new Color[50];



 9:    Thread m_thThread;



10:



11:    public void start()



12    {



13:        if (runThread == null)



14:        {



15:            m_thThread = new Thread(this);



16:            m_thThread.start();



17:        }



18:    }



19:



20:    public void stop()



21:    {



22:        if (m_thThread != null)



23:        {



24:            m_thThread.stop();



25:            m_thThread = null;



26:        }



27:    }



28:



29:    public void run()



30:    {



31:        // initialize the color array



32:        float fColor = 0;



33:        int i;



34:        for (i = 0; i < m_clrColors.length; i++)



35:        {



36:            m_clrColors[i] =



37:                Color.getHSBColor(fColor, (float)1.0,(float)1.0);



38:            fColor += .02;



39:        }



40:



41:         // cycle through the colors



42:        i = 0;



43:        while (true)



44:        {



45:            setForeground(m_clrColors[i]);



46:            repaint();



47:            i++;



48:            try



49:            {



50:                 Thread.sleep(50);



51:            }



52:            catch (InterruptedException e)



53:            {   }



54:            if (i == m_clrColors.length )



55:            {



56:                 i = 0;



57:            }



58:        }



59:    }



60:



61:    public void paint(Graphics g)



62:    {



63:        g.setFont(m_fntF);



64:        g.drawString("All the Swirly Colors", 15, 50);



65:    }



66: }

Figure 11.2. The ColorSwirl applet.

Compile and run this applet so you can observe the horrible screen flicker. Of course, if you're running a dual processor P6-300 (or whatever the hardware manufacturers are up to by the time this book is published), you might not notice the severity of the screen flicker and you might just have to take our word for it. Even so, keep in mind that the average user system might be 486-66!

There are three new things to note about this applet that might look strange to you:

Now that you understand what the applet does, fix the flicker. The flicker here results because each time the applet is painted, there's a moment where the screen is cleared. Instead of the text cycling neatly from red to a nice pink to purple, it's going from red to gray, to pink to gray, to purple to gray, and so on—not very nice looking at all.

Because the clearing of the screen is all that's causing the problem, the solution is easy: Override update() and remove the part where the screen gets cleared. It doesn't really need to get cleared anyhow, because nothing is changing except the color of the text. With the screen clearing behavior removed from update(), all update needs to do is call paint(). Here's what you should add to this applet to override the update() method:




public void update(Graphics g)



{



   paint;



}

With that—just one small, three-line addition—no more flicker. Wasn't that easy?

Solution Two—Redraw Only What You Have To


For some applets, it won't be quite that easy. Here's another example. In this applet, called Checkers, a red oval (a checker piece) moves from a black square to a white square and back again, as if on a checkerboard. Listing 11.4 shows the code for this applet, and Figure 11.3 shows the applet itself.

Listing 11.4. The Checkers applet.




 1:   import java.awt.Graphics;



 2:   import java.awt.Color;



 3:



 4:   public class Checkers extends java.applet.Applet implements Runnable



 5: {



 6:       Thread m_thRunner;



 7:       int m_ixpos;



 8:



 9:       public void start()



10:       {



11:          if (m_thRunner == null)



12:          {



13:              m_thRunner = new Thread(this);



14:              m_thRunner.start();



15:          }



16:       }



17:



18:      public void stop()



19:      {



20          if (m_thRunner != null)



21          {



22:              m_thRunner.stop();



23               m_thRunner = null;



24:          }



25:      }



26:



27:      public void run()



28       {



29:          setBackground(Color.blue);



30:          while (true)



31:          {



32:              for (m_ixpos = 5; m_ixpos <= 105; m_ixpos+=4)



33:              {



34:                  repaint();



35:                  try



36:                  {



37:                     Thread.sleep(100);



38:                  }



39:                  catch (InterruptedException e)



40:                  {    }



41:              }



42:              for (m_ixpos = 105; m_ixpos > 5; m_ixpos -=4)



43:              {



44:                  repaint();



45:                  try



46:                  {



47:                      Thread.sleep(100);



48:                  }



49                   catch (InterruptedException e)



50:                  {    }



51:              }



52:          }



53:      }



54:



55:      public void paint(Graphics g)



56:     {



57:          // Draw background



58:          g.setColor(Color.black);



59:          g.fillRect(0, 0, 100, 100);



60:          g.setColor(Color.white);



61:          g.fillRect(101, 0, 100, 100);



62:



63:          // Draw checker



64:          g.setColor(Color.red);



65:          g.fillOval(m_ixpos, 5, 90, 90);



66:      }



67:  }

Figure 11.3. The Checkers applet.

Here's a quick run-through of what this applet does: a data member, m_ixpos, keeps track of the current starting position of the checker (because it moves horizontally, the y stays constant and the x changes). In the run() method, you change the value of x and repaint, waiting 100 milliseconds between each move. The checker moves from one side of the screen to the other and then moves back (hence the two for loops in that method).

In the actual paint() method, the background squares are painted (one black and one white), and then the checker is drawn at its current position.

This applet, like the swirling colors applet, also has a terrible flicker. (In line 29, the background is blue to emphasize it, so if you run this applet you'll definitely see the flicker.)

However, the solution to solving the flicker problem for this applet is more difficult than for the last one, because you actually want to clear the screen before the next frame is drawn. Otherwise, the red checker won't have the appearance of leaving one position and moving to another; it'll just leave a red smear from one side of the checkerboard to the other.

How do you get around this? You still clear the screen, in order to get the animation effect; but, rather than clearing the entire screen, you clear only the part that you actually changed. By limiting the redraw to only a small area, you can eliminate much of the flicker you get from redrawing the entire screen.

To limit what gets redrawn, you need a couple of things. First, you need a way to restrict the drawing area so that each time paint() is called, only the part that needs to get redrawn actually gets redrawn. Fortunately, this is easy by using a mechanism called clipping.

Clipping, part of the graphics class, enables you to restrict the drawing area to a small portion of the full screen; although the entire screen might get instructions to redraw, only the portions inside the clipping area are actually drawn.

The second thing you need is a way to keep track of the actual area to redraw. Both the left and right edges of the drawing area change for each frame of the animation (one side to draw the new oval, the other to erase the bit of the oval left over from the previous frame), so to keep track of those two x values, you need data members for both the left and the right side.

With those two concepts in mind, start modifying the Checkers applet to redraw only what needs to be redrawn. First, you'll add data members for the left and right edges of the drawing area. Call those data members m_ix1 and m_ix2 (u for update), where m_ix1 is the left side of the area to draw and m_ix2 the right.




int m_ix1,m_ix2;

Now modify the run() method so that it keeps track of the actual area to be drawn—which you would think is easy—just update each side for each iteration of the animation. Here, however, things can get complicated because of the way Java uses paint() and repaint().

The problem with updating the edges of the drawing area with each frame of the animation is that for every call to repaint() there might not be an individual corresponding paint(). If system resources get tight (because of other programs running on the system, or for any other reason), paint() might not get executed immediately and several calls to paint() might queue up waiting for their turn to change the pixels on the screen. In this case, rather than trying to make all those calls to paint() in order (and be potentially behind all the time), Java catches up by executing only the most recent call to paint() and skips all the others.

If you update the edges of the drawing area with each repaint(), and a couple of calls to paint() are skipped, you end up with bits of the drawing surface not being updated and bits of the oval left behind. There's a simple way around this: update the leading edge of the oval each time the frame updates, but only update the trailing edge if the most recent paint has actually occurred. This way, if a couple of calls to paint() get skipped, the drawing area will get larger for each frame—and when paint() finally gets caught up, everything will get repainted correctly.

Yes, this is horrifyingly complex. If I could have written this applet more simply, I would have. But without this mechanism, the applet will not get repainted correctly. Try stepping through it slowly in the code so you can get a better grasp of what's going on at each step.

Start with run(), where each frame of the animation takes place. Here's where you calculate each side of the drawing area based on the old position of the oval and the new position of the oval. When the oval is moving toward the left side of the screen, this is easy. The value of m_ix1 (the left side of the drawing area) is the previous oval's x position (xpos), and the value of m_ix2 is the x position of the current oval plus the width of that oval (90 pixels in this example).

To refresh your memory, here's what the old run() method looked like:




public void run()



{



     setBackground(Color.blue);



     while (true)



     {



         for (m_ixpos = 5; m_ixpos <= 105; m_ixpos+=4)



         {



             repaint();



             try



             {



                 Thread.sleep(100);



             }



             catch (InterruptedException e)



             {    }



         }



         for (m_ixpos = 105; m_ixpos > 5; m_ixpos -=4)



         {



             repaint();



             try



             {



                 Thread.sleep(100);



             }



             catch (InterruptedException e)



             {    }



         }




}

In the first for loop in the run() method, where the oval is moving toward the right, you first update m_ix2 (the right edge of the drawing area):




m_ix2 = m_ixpos + 90;

Then, after the repaint() has occurred, you update m_ix1 to reflect the old x position of the oval. However, you want to update this value only if the paint actually happened. How can you tell if the paint actually happened? You can reset m_ix1 in paint() to a given value (0), and then test to see whether you can update that value or whether you have to wait for the paint() to occur:




if (m_ix1 == 0)



{



      m_ix1 = m_ixpos;



}

Here's the new, completed for loop for when the oval is moving to the right:




for (m_ixpos = 5; m_ixpos <= 105; m_ixpos += 4)



{



    m_ix2 = m_ixpos + 90;



    repaint();



    try



    {



        Thread.sleep(100);



    }



    catch (InterruptedException e)



    {    }



    if (m_ix1 == 0)



    {



        m_ix1 = m_ixpos;



    }



}

When the oval is moving to the left, everything flips. m_ix1, the left side, is the leading edge of the oval that gets updated every time, and m_ix2, the right side, has to wait to make sure it gets updated. So, in the second for loop, you first update m_ix1 to be the x position of the current oval:




m_ix1 = m_ixpos;

Then, after the repaint() is called, you test to make sure the paint happened and update m_ix2:




if (m_ix2 == 0)



{



     m_ix2 = m_ixpos + 90;



}

Here's the new version of the second for loop inside run():




for (m_ixpos = 105; m_ixpos > 5; m_ixpos -= 4)



{



    m_ix1 = m_ixpos;



    repaint();



    try



    {



        Thread.sleep(100);



    }



    catch (InterruptedException e)



    {    }



    if (m_ix2 == 0)



    {



         m_ix2 = m_ixpos + 90;



    }



}

Those are the only modifications run() needs. Override update() to limit the region that is being painted to the left and right edges of the drawing area that you set inside run(). To clip the drawing area to a specific rectangle, use the clipRect() method. clipRect(), like drawRect(), fillRect(), and clearRect(), is defined for graphics objects and takes four arguments: x and y starting positions, and the width and the height of the region.

Here's where m_ix1 and m_ix2 come into play. m_ix1 is the x point of the top corner of the region; then use m_ix2 to get the width of the region by subtracting m_ix1 from that value. Finally, to finish update(), you call paint():




public void update(Graphics g)



{



    g.clipRect(m_ix1, 5, m_ix2 - m_ix1, 95);



    paint;



}

With the clipping region in place, you don't have to do anything to the actual paint() method. paint() goes ahead and draws to the entire screen each time, but only the areas inside the clipping region actually get changed on screen.

You need to update the trailing edge of each drawing area inside paint() in case several calls to paint() are skipped. Because you're testing for a value of 0 inside run(), you merely reset m_ix1 and m_ix2 to 0 after drawing everything:




m_ix1 = m_ix2 = 0;

These are the only changes you have to make to this applet in order to draw only the parts of the applet that changed (and to manage the case where some frames don't get updated immediately). Although this doesn't totally eliminate flickering in the animation, it does reduce it a great deal. Try it and see. Listing 11.5 shows the final code for the Checkers applet.

Listing 10.5. The final Checkers applet.




 1:   import java.awt.Graphics;



 2:   import java.awt.Color;



 3:



 4:   public class Checkers extends java.applet.Applet implements Runnable



 5:  {



 6:       Thread m_thRunner;



 7:       int m_ixpos;



 8:       int m_ix1,m_ix2;



 9:



 10:       public void start()



11:      {



12:          if (m_thRunner == null)



13:         {



14:              m_thRunner = new Thread(this);



15:              m_thRunner.start();



16:          }



17:       }



18:



19:      public void stop()



20:     {



21          if (m_thRunner != null)



22          {



23:              m_thRunner.stop();



24               m_thRunner = null;



25:          }



26:      }



27:



28:      public void run()



29:      {



30:          setBackground(Color.blue);



31:          while (true)



32:          {



33:              for (m_ixpos = 5; m_ixpos <= 105; m_ixpos+=4)



34:              {



35:                  m_ix2 = m_ixpos + 90;



36:                  repaint();



37:                  try



38:                  {



39:                     Thread.sleep(100);



38:                  }



39:                  catch (InterruptedException e)



40:                  {    }



41:                  if (m_ix1 == 0)



42:                  {



43:                     m_ix1 = m_ixpos;



44:                  }



45:              for (m_ixpos = 105; m_ixpos > 5; m_ixpos -=4)



46:              {



47:                  m_ix1 = m_ixpos;



48:                  repaint();



49:                  try



50:                  {



51:                      Thread.sleep(100);



52:                  }



53:                  catch (InterruptedException e)



54:                  {    }



55:                  if (m_ix2 == 0)



56:                  {



57:                      m_ix2 = m_ixpos + 90;



58:                  }



59:              }



60:          }



61:      }



62:      public void paint(Graphics g)



63:     {



64:          // Draw background



65:          g.setColor(Color.black);



66:          g.fillRect(0, 0, 100, 100);



67:          g.setColor(Color.white);



68:          g.fillRect(101, 0, 100, 100);



69:



70:          // Draw checker



71:          g.setColor(Color.red);



72:          g.fillOval(m_ixpos, 5, 90, 90);



73:          m_ix1 = m_ix2 = 0;



74:      }



75:     public void update(Graphics g)



76:     {



77:         g.clipRect(m_ix1, 5, m_ix2 - m_ix1, 95);



78:         paint;



79:     }



80:  }

Summary


Congratulations on getting through Day 11! This day was a bit rough; you've learned a lot, and it all might seem overwhelming. You learned about a plethora of methods to use and override: start(), stop(), paint(), repaint(), run(), and update()—and you got a solid foundation in creating and using threads.

After today, you're over the worst hurdles in terms of understanding applets. Other than handling bitmap images, which you'll learn about tomorrow, you now have the basic background to create just about any animation you want in Java.

Q&A


Q: Why all the indirection with paint(), repaint(), update(), and all that? Why not have a simple paint method that just puts stuff on the screen when you want it there?

A: The Java AWT toolkit enables you to nest drawable surfaces within other drawable surfaces. When a paint() takes place, all the parts of the system are redrawn, starting from the outermost surface and moving downward into the most nested one. Because the drawing of your applet takes place at the same time everything else is drawn, your applet doesn't get any special treatment; your applet will be painted when everything else is painted. Although with this system you sacrifice some of the immediacy of instant painting, it enables your applet to coexist with the rest of the system more cleanly.

Q: Are Java threads like threads on other systems?

A: Java threads have been influenced by other thread systems, and if you're used to working with threads, many of the concepts in Java threads will be very familiar to you. You learned the basics today; you'll learn more next week on Day 20.

Q: When an applet uses threads, I just have to tell the thread to start and it starts, and tell it to stop and it stops? That's it? I don't have to test anything in my loops or keep track of its state? It just stops?

A: It just stops. When you put your applet into a thread, Java can control the execution of your applet much more readily. By causing the thread to stop, your applet just stops running, and then resumes when the thread starts up again. Yes, it's all automatic. Neat, isn't it?

Q: The Swirling Colors applet seems to display only five or six colors. What's going on here?

A: This is the same problem that you ran into yesterday wherein, on some systems, there might not be enough colors to be able to display all of them reliably. If you're running into this problem, other than upgrading your hardware, you might try quitting other applications running on your system that use color. Other browsers or color tools in particular might be hogging colors that Java wants to be able to use.

Q: Even with the changes you made, the Checkers applet still flickers.

A: And, unfortunately, it will continue to do so. Reducing the size of the drawing area by using clipping does significantly reduce the flickering, but it doesn't stop it entirely. For many applets, using either of the methods described today might be enough to reduce animation flicker to the point where your applet looks good. To get totally flicker-free animation, you'll need to use a technique called double-buffering, which you'll learn about tomorrow.

Previous Page Page Top TOC Next Page