Introducing the Basic Animation Class

jeffheaton's picture

In the last two sections we saw how to use double buffering and multithreading. Now we will develop a class that ties these two technologies together. This class, named "BasicAnimate", will be the base class for the animations produced in this series of articles. The "BasicAnimate" class will be enhanced several times during this article series. At the end, you will be left with a powerful set of classes that you can use to build animated applications of your own. For now, we will take a look at the first incarnation of the "BasicAnimate" class.

By itself the "BasicAnimate" class cannot be ran, and thus displays nothing. To be of any use, it must be extended. In the next two sections I will show you how to extend the BasicAnimate class. But first, lets take a look at the "BasicAnimate" class. The "BasicAnimate" class can be seen in Listing 3.3.

Listing 3.3: The Basic Animation class (BasicAnimate .java)

import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class BasicAnimate extends Applet implements Runnable
{
  /**
   * The thread that is used to drive the animation.
   */
  private Thread thread = null;

  /**
   * The off-screen image used for double buffering.
   */
  private Image offscreenImage = null;

  /**
   * The off-screen graphics context, used to draw to
   * the off-screen image.
   */
  private Graphics offscreenGraphics = null;

  /**
   * The primary graphics context, drawing to this
   * context will draw directly to the applet.
   */
  private Graphics primaryGraphics = null;

  /**
   * If done is set to true, then the applet will
   * stop.
   */
  private boolean done;

  /**
   * Increases one for each "pulse", or frame of animation.
   */
  private long pulseCount;

  /**
   * How many miliseconds should each pulse last.
   */
  private int pulseLength = 100;

  /**
   * Use this constant to specify big-endian integers.
   */
  private boolean doubleBuffer = true;

  /**
   * This method sets up for applet or Frame usage. For an
   * applet, it will be called automatically. For a frame, you
   * should call this method as part of your setup.
   *
   * @param f The file to read/write from/to.
   */
  public void init()
  {
    thread = new Thread(this);

    offscreenImage = createImage(getWidth(), getHeight());

    offscreenGraphics = offscreenImage.getGraphics();
    primaryGraphics = getGraphics();
    pulseCount = 0;
    thread.start();
  }

  /**
   * This method is called to paint the off-screen image.
   * It is called for each frame of animation. You should
   * override this method in derived classes. To find out
   * what "frame" the animation is on access the pulseCount
   * property.
   *
   * @param g The off-screen graphics object to paint to.
   */
  public void paintOffscreen(Graphics g)
  {
    // paint the off-screen image
    g.setColor(Color.black);
    g.fillRect(0, 0, getWidth(), getHeight());
  }

  /**
   * Used internally to drive the animation. You should not
   * generally need to override this method.
   *
   * @param f The file to read/write from/to.
   */
  public void run()
  {
    done = false;
    while (!done)
    {
      try
      {
        // pause for this frame
        Thread.sleep(pulseLength);

        // draw the off-screen image
        if (doubleBuffer)
          paintOffscreen(offscreenGraphics);
        else
          paintOffscreen(primaryGraphics);

        // paint off-screen image to primary display, if needed
        if (doubleBuffer)
          primaryGraphics.drawImage(offscreenImage, 0, 0, this);

        // increase the pulse count
        pulseCount++;
      } catch (InterruptedException e)
      {
      }
    }
  }

  public void setPulseLength(int pulseLength)
  {
    this.pulseLength = pulseLength;
  }

  public int getPulseLength()
  {
    return pulseLength;
  }

  public void setDoubleBuffer(boolean doubleBuffer)
  {
    this.doubleBuffer = doubleBuffer;
  }

  public boolean getDoubleBuffer()
  {
    return doubleBuffer;
  }

  public void setPulseCount(long pulseCount)
  {
    this.pulseCount = pulseCount;
  }

  public long getPulseCount()
  {
    return pulseCount;
  }

}

This class is somewhat lengthy. Documentation is provided in the source code to explain what each of the methods and variables are for. We will now examine the overall flow of this base class.

The "BasicAnimate" class begins when the "init" method is called. The "init" method performs the following operations.

  • Starts the background thread, which drives the animation
  • Creates an off-screen image for double buffering
  • Creates a Graphics object that draws to the off-screen image
  • Creates a Graphics object that draws to the screen

Once the "init" method completes, the background thread begins executing the "run" method. The run method will begin the "pulse" of the animated application. As the run method executes it repeatedly calls the "paintOffscreen" method. This method should draw animation. Each call to "paintOffscreen" should draw successive stages of the animation. The variable "pulseCount" will track how many pulses, or calls to paintOffscreen, have occurred. The "pulseCount" can be used to time when certain parts of the animation should occur. More will be covered on this later.

As you can see, most of the action will occur in the "paintOffscreen" method. Anything you paint to the "Graphics" object provided by the "paintOffscreen" method will be drawn to the off-screen image. When you are done, the off-screen image will be painted to the screen. This allows all of your drawing operations to be "flashed" to the screen in one shot. This greatly reduces flicker.

To create your own animation, you should override the "paintOffscreen" method. In the next two sections, I will show you how to do that.


Copyright 2005 - 2012 by Heaton Research, Inc.. Heaton Research™ and Encog™ are trademarks of Heaton Research. Click here for copyright, license and trademark information.