Images, Drawn Graphics, and Event Handling in a MIDlet

Learn how to paint images, draw graphics, and do event handling on a cell phone screen.

Published:  September 9, 2008
By Richard G. Baldwin


Preface

This is the second part of a two-part lesson in a series of tutorial lessons designed to teach you how to write programs using the Sun Java Wireless Toolkit for CLDC.  The first lesson in the series was titled Getting Started with MIDlets and the Sun Java Wireless Toolkit for CLDC.  The previous lesson was titled Programming MIDlet Graphics using the Canvas Class (see Resources).

What you will learn

In this lesson, you will learn how to mix image file data and drawn graphics on a cell phone screen.  You will learn how to draw various shapes such as rectangles, circles, arcs, filled triangles, and rounded rectangles.  You will learn how to use an off-screen image, and how to use the event handling capability of the Canvas class for simple animation.  You will also learn how to make your MIDlet animations efficient by minimizing the screen area that must be repainted.

Viewing tip

I recommend that you open another copy of this document in a separate browser window and use the following links to easily find and view the figures and listings while you are reading about them.

Figures

Listings

Supplementary material

I recommend that you also study the other lessons in my extensive collection of online Java tutorials.  You will find a consolidated index at www.DickBaldwin.com.

General background information

As I told you in Part 1 of this lesson, if you are familiar with graphics programming in J2SE without the benefit of Java 2D and Java 3D, you will probably be reasonably comfortable with the material in this lesson.  J2ME graphics is very similar to graphics programming in the early days of Java, but with some interesting new wrinkles.

Partial class hierarchy for MIDP 2.0

A partial class hierarchy for MIDP 2.0 is shown in Figure 1.  Figure 1 includes all of the classes that are contained in the package named javax.microedition.lcdui.  In addition, Figure 1 includes three classes that are contained in other packages.

Figure 1. Partial class hierarchy for MIDP 2.0.
  • Object
    • Displayable
      • Screen
        • TextBox
        • Alert
        • List
        • Form
      • Canvas (abstract)
        • GameCanvas (abstract)
    • Display
    • Ticker
    • AlertType
    • Image
    • Item
      • Gauge
      • ChoiceGroup
      • CustomItem
      • DateField
      • ImageItem
      • Spacer
      • StringItem
      • TextField
    • Timer
    • TimerTask
    • Command
    • Graphics
    • Font
 

I have discussed and illustrated all of the classes shown in boldface (plus the Choice interface, the CommandListener interface, and the ItemCommandListener interface) in earlier lessons (see Resources).  I discussed the Canvas class and the Graphics class in Part 1 of this lesson.  I will expand my discussion of those two classes in Part 2.

I will discuss and illustrate the GameCanvas class in a future lesson.  I will leave the CustomItem class as an exercise for the student.

Preview

I will present and explain two MIDlets in this lesson.  I will begin with the MIDlet program named Canvas03.  This MIDlet will introduce three major programming concepts not covered in earlier lessons:

In addition, this MIDlet will illustrate a variety of drawing capabilities of the Graphics class including:

Output from the MIDlet named Canvas03 in Sun emulator

Figure 2 shows an output from the MIDlet named Canvas03 when run in the Sun cell phone emulator.

Figure 2. Output from the MIDlet named Canvas03 in Sun emulator.

Simple animation

When the MIDlet first starts running, the large black rectangle containing the three images and the drawn graphics are centered on the screen.  The arrow keys on the emulator keypad can be used to move the rectangle and its contents to the position shown in Figure 2, or to any other location on the screen.

The MIDlet named Canvas04

When I have finished explaining the MIDlet named Canvas03, I will present and explain an updated version of the MIDlet named Canvas04.  To all outward appearances, the behavior of this MIDlet is the same as the previous one.  However, it contains several modifications that make it more efficient during the animation.

Discussion and sample code

 
MIDlet testing
The MIDlet presented in this lesson was tested using a Java SE 6 compiler, targeted at a V1.4 virtual machine, and WTK 2.5.2 running under Windows XP.

The MIDlet named Canvas03

Being a little more specific than before, the purpose of this MIDlet is to illustrate the following major programming concepts involving the use of the Canvas class and the Graphics class:

In addition, the MIDlet implements an EXIT command that can be used to terminate the MIDlet and cause it to enter the destroyed state.

Will discuss in fragments

I will present and explain this MIDlet code in fragments.  A complete listing of the MIDlet code is shown in Listing 24 near the end of the lesson.

Beginning of the class for the MIDlet named Canvas03

The beginning of the class, including the beginning of the constructor is shown in Listing 1.

Listing 1. Beginning of the class for the MIDlet named Canvas03.
public class Canvas03 extends MIDlet{
  Canvas myCanvas;

  public Canvas03(){//constructor
    System.out.println("Construct MIDlet");
    myCanvas = this.new MyCanvas();
    
    //Guarentee that the screen gets painted.
    myCanvas.repaint();

Listing 1 instantiates an object of the member class named MyCanvas and then calls the method named repaint on that object to guarantee that it is properly painted on the cell phone emulator screen.  This produces the output shown in Figure 2, except that the large black rectangle is centered in the emulator screen at this point.

Add and register an EXIT command on the Canvas

Listing 2 adds an EXIT command to the Canvas and registers a CommandListener object on the Canvas to cause the MIDlet to enter the destroyed state when the command is activated.

Listing 2. Add and register an EXIT command on the Canvas.
    //Add an EXIT command and register a CommandListener
    // to handle the EXIT command.
    myCanvas.addCommand(new Command("EXIT",
                                         Command.EXIT,2));
    myCanvas.setCommandListener(
      new CommandListener(){
        public void commandAction(
                          Command cmd,Displayable source){
          if(cmd.getCommandType() == Command.EXIT){
            destroyApp(true);
          }//end if
        }//end commandAction
      }//end new CommandListener
    );//end setCommandListener
  }//end constructor

Because this is the only command that is registered on the Canvas, and because it is one of the commands that are eligible for occupying the position of the left soft key in the Sun cell phone emulator, the EXIT command appears at the bottom-left of the screen in Figure 2.

The remainder of the outer class named Canvas03

The remainder of the outer class named Canvas03 is shown in Listing 3.

Listing 3. The remainder of the outer class named Canvas03.
  public void startApp(){
    //Make the Canvas the current display.
    Display.getDisplay(this).setCurrent(myCanvas);
  }//end startApp

  public void pauseApp(){
  }//end pauseApp

  public void destroyApp(boolean unconditional){
    System.out.println("Destroy MIDlet");
    notifyDestroyed();
  }//end destroyApp

Everything in Listing 3 has been covered in earlier lessons (see Resources), so Listing 3 shouldn't require further explanation.

The beginning of the member class named MyCanvas

Listing 4 shows the beginning of the member class named MyCanvas.

Listing 4. The beginning of the member class named MyCanvas.
  class MyCanvas extends Canvas{
    Image redBallImage;
    Image greenBallImage;
    Image blueBallImage;
    int frameWidth = 200;
    int frameHeight = 200;
    int redBallWidth = 0;
    int redBallHeight = 0;
    int greenBallWidth = 0;
    int greenBallHeight = 0;
    int blueBallHeight = 0;
    int blueBallWidth = 0;
    int horizBorder = 50;
    int vertBorder = 50;
    int horizDrawPoint = 0;
    int vertDrawPoint = 0;

There is no technical requirement for the class named MyCanvas to be a member class.  I made it a member class strictly for programming convenience.  It is necessary, however, for the class to extend the library class named Canvas.

The code in Listing 4 declares a large number of working variables and initializes some of them.  (Hopefully you will recognize that initialization of the int variables to a value of zero is not necessary because that is the default.  I did that simply for clarity.)

The constructor

This is where things start to get interesting.  Listing 5 shows the constructor for the class named MyClass.

Listing 5. The constructor for the class named MyCanvas.
    MyCanvas(){//constructor
      setFullScreenMode(false);
      horizDrawPoint = getWidth()/2;
      vertDrawPoint = getHeight()/2;
    }//end constructor

The setFullScreenMode method

As you learned in a previous lesson, the first statement in the constructor is superfluous because false is the default value.  I included this statement to make it easy for you experiment with the difference between normal mode and full-screen mode.  If you set the setFullScreenMode parameter to true, the display will change to full-screen mode.  Making this change will hide the EXIT command in the Sun cell phone emulator.  (I don't know how to expose the command in full-screen mode.)

Initialize the drawing point coordinate values

The last two statements in Listing 5 are not superfluous.  The contents of the variables named horizDrawPoint and vertDrawPoint will be used later to position the image on the emulator screen.  The code in Listing 5 initializes the values of the two variables to point to the center of the screen.

Beginning of the overridden paint method

The overridden paint method begins in Listing 6.  Recall that this method will be called whenever it is necessary to display the Canvas object on the cell phone screen.

Listing 6. Beginning of the overridden paint method.
    public void paint(Graphics g){
      try{
        //Create three Image objects
        redBallImage = Image.createImage(
                                 "/Canvas03/redball.PNG");
        greenBallImage = Image.createImage(
                               "/Canvas03/greenball.PNG");
        blueBallImage = Image.createImage(
                                "/Canvas03/blueball.PNG");

Three image objects

The code in Listing 6 creates three objects of the class Image that point to three image files with the following names:

You should have no difficulty recognizing the three images resulting from these files in the MIDlet output in Figure 2.  They are the spheres at the vertices of the olive-drab triangle.

I first discussed the use of the method named createImage to create Image objects from image files in the earlier lesson titled Using Alerts, Images, Timers, and Gauges in MIDlets (see Resources).  You may find it useful to go back and review that lesson to refresh your memory on this topic.

You will need some image files

If you run this MIDlet, you will need to provide three small image files to replace the image files in the above list.  Be sure to copy those files into the same directory as the directory that contains your source code.  Either change the names of your image files to match the names of my files, or change the code in Listing 6 to match the names of your image files.

Get and save the dimensions of the three images

Having created the three Image objects, the code in Listing 7 calls methods of the Image class to get and save the width and height of each image.

Listing 7. Get and save the dimensions of the three images.
        redBallWidth = redBallImage.getWidth();
        redBallHeight = redBallImage.getHeight();
        greenBallWidth = greenBallImage.getWidth();
        greenBallHeight = greenBallImage.getHeight();
        blueBallWidth = blueBallImage.getWidth();
        blueBallHeight = blueBallImage.getHeight();

Although all of my images have the same width and height, and the width is equal to the height, that should not be a requirement for your images.  The code in Listing 7 is written to accommodate images of different sizes and shapes.

Paint the screen white

Listing 8 begins by setting the drawing color to white and then painting the entire screen with the new drawing color.

Listing 8. Paint the screen white.
        g.setColor(0xffffff);
        g.fillRect(0,0,getWidth(),getHeight());
        
        g.setColor(0x000000);//Set drawing color to black

This is accomplished by drawing a filled rectangle with dimensions that match the dimensions of the Canvas object.  Note however that because Figure 2 was run with the Canvas in normal mode as opposed to full-screen mode, the gray bar at the bottom of the screen was not painted over.

MIDlet output without clearing the screen

If the screen had not been painted white in Listing 8, the subsequent drawing would simply have been placed on top of what was already there.  This would not only allow the screen that was showing before the MIDlet was launched to show through, it would also produce multiple images of the newly drawn material as the arrow keys are used to move the new material around on the screen.  A sample of this phenomenon is shown in Figure 3.

Figure 3. MIDlet output without clearing the screen.

Set the drawing color to black

Listing 8 sets the drawing color to black after the screen has been painted white.

Create an off-screen image

The code in Listing 9 is completely new to this series of lessons.

Listing 9. Create an off-screen image.
        Image osi = Image.createImage(frameWidth,
                                      frameHeight);

The Image class in MIDP 2.0 provides six different overloaded versions of the method named createImage.  Five of those methods create immutable Image objects from various sources of image data.  Here is part of what Sun has to say about the sixth overloaded version that is used in Listing 9:

"Creates a new, mutable image for off-screen drawing. Every pixel within the newly created image is white. The width and height of the image must both be greater than zero."

The fact that the image is mutable means that it may be modified.  In other words, that means it will be possible for the MIDlet to either draw on the off-screen image or deposit image file data on the off-screen image or both.

Get a Graphics object representing the off-screen Image

The overridden paint method always receives an incoming reference to an object of the class Image.  You can think of the Image object as representing the screen.  When you draw on the object, the results of that drawing effort will appear on the screen (when the Canvas object is displayed on the screen).

When you need to draw on an off-screen image, you need to get a Graphics object that represents the off-screen image (all drawing methods are methods of the Graphics class).  Listing 10 gets such a Graphics object.

Listing 10. Get a Graphics object representing the off-screen image.
        Graphics osg = osi.getGraphics();

What does Sun have to say about the getGraphics method?

Here is part of what Sun has to say about the getGraphics method of the Image class:

"Creates a new Graphics object that renders to this image...

The newly created Graphics object has the following properties:

The lifetime of Graphics objects created using this method is indefinite. They may be used at any time, by any thread."

It is important to note that the off-screen image is not an object of the class Canvas.  Rather it is an object of the class Image.  The Graphics object that is received as a parameter to the overridden paint method "renders to" the Canvas object, whereas the Graphics object obtained in Listing 10 "renders to" the Image object.

What can you do with an off-screen image?

Presumably, if you knew how to do it, you could create an off-screen Image object and then write it out into an image file somewhere.  However, we won't attempt to do that in this MIDlet.  In this MIDlet, we will eventually cause the contents of the off-screen image to be drawn on the Canvas and displayed on the cell phone screen.

Draw a rectangular border on the off-screen Image object

Listing 11 calls the drawRect method of the Graphics class to draw a rectangle around the edge of the off-screen Image object.  (The width and height of the off-screen image was established in Listing 9.)  This produces the black rectangle shown in Figure 2.

Listing 11. Draw a rectangular border on the off-screen Image object.
        osg.drawRect(0,0,frameWidth-1,frameHeight-1);

Note the requirement to set the width and height dimensions of the rectangle to one less than the corresponding dimensions of the off-screen Image object.  Otherwise, the lines representing the right and bottom sides of the rectangle would be off the edge of the off-screen Image object.

Draw a filled triangle on the off-screen Image object

After setting the drawing color to that shown in Figure 2, the code in Listing 12 calls the fillTriangle method of the Graphics class to draw a filled triangle on the off-screen Image with its vertices specified by the three pairs of coordinate values shown in Listing 12.

Listing 12. Draw a filled triangle on the off-screen Image object.
        osg.setColor(0xa0a000);
        osg.fillTriangle(horizBorder,
                         vertBorder,
                         frameWidth - horizBorder,
                         vertBorder,
                         frameWidth/2,
                         frameHeight - vertBorder);

I will leave it as an exercise for the student to decipher how the coordinate values contained in the variables referenced in Listing 12 result in the vertices shown in Figure 2 or the vertices shown in Figure 3.

What about the color?

Up to this point, I have been deferring a discussion of the color value until a later time.  That time has arrived.

The parameter that is passed to the setColor method in Listing 12 is a 32-bit value of type int represented by the following three hexadecimal values:

A mixture of three primary colors

Each hexadecimal value represents a decimal value with a range from 0 to 255.  Each one of the three values represents the contribution to a color mixture of one of the following primary colors:

A value of 0 (0x00) results in no contribution of the associated primary color to the mix.  A value of 255 (0xff) results in a maximum contribution of the associated primary color to the mix.

The color value in Figure 12 contains a little bit of red (a0), a little bit of green (a0), and no blue (00).  This produces the olive-drab color shown by the filled triangle in Figure 2.

Draw three images on the off-screen Image object

Listing 13 calls the drawImage method of the Graphics class three times in succession to cause the three small Image objects created in Listing 6 to be drawn on the larger off-screen Image object created in Listing 9.

Listing 13. Draw three images on the off-screen Image object.
        osg.drawImage(redBallImage,
                      horizBorder,
                      vertBorder,
                      Graphics.HCENTER|Graphics.VCENTER);
                      
        osg.drawImage(greenBallImage,
                      frameWidth - horizBorder,
                      vertBorder,
                      Graphics.HCENTER|Graphics.VCENTER);
                      
        osg.drawImage(blueBallImage,
                      frameWidth/2,
                      frameHeight - vertBorder,
                      Graphics.HCENTER|Graphics.VCENTER);

Specifying the image and the coordinate values

The first parameter to the drawImage method specifies the image that is to be drawn.  The next two parameters specify a pair of horizontal and vertical coordinate values.  Note that the coordinate values specified for each image in Listing 13 matches the coordinates of a triangle vertex in Listing 12.

The anchor-point value

The last parameter to the drawImage method is an anchor-point value.  You learned about the use of anchor points with text in Part 1 of this lesson (see Resources).  At that time, I told you that the anchor-point concept applies not only to text but to images as well.  I also told you that the VCENTER anchor-point constant can be used with images but not with text, and the BASELINE anchor-point constant can be used with text but not with images.  The HCENTER anchor-point constant can be used with either images or text.

In all three cases in Listing 13, the anchor-point value passed to the method was constructed from the bitwise inclusive OR of the HCENTER and VCENTER anchor-point constants.  This means that the image will be drawn with its center located at the position specified by the horizontal and vertical coordinate values.  In this case, it means that each image will be drawn on one vertex of the filled triangle as shown in Figure 2.

Draw a red rectangle around the red ball image

Listing 14 calls the drawRect method of the Graphics class to draw a red rectangle around the red ball image as shown in Figure 2.

Listing 14. Draw a red rectangle around the red ball image.
        osg.setColor(0xff0000);
        osg.drawRect(horizBorder - redBallWidth,
                     vertBorder - redBallHeight,
                     redBallWidth * 2,
                     redBallHeight * 2);

The width and the height of the red rectangle are specified to be twice the width and twice the height of the image.  This code should work properly with your image even if it has different dimensions than my image.

No anchor point concept applies here

Note that shapes drawn with methods like drawRect don't support the concept of an anchor point parameter.  Therefore, it is necessary to read the documentation for the method and then make arithmetic adjustments to the parameters to specify the location and size of the shape.

For the drawRect method, the first two parameters specify the location of the upper-left corner of the rectangle and the next two parameters specify the width and height of the rectangle.  Therefore, it was necessary to adjust the values for those parameters to center the rectangle on the red ball image and to set its width and height accordingly.

How do you draw a circle?

Neither J2SE 1.6 nor MIDP 2.0 provides a drawCircle method.  However, J2SE provides a drawOval method that makes it relatively easy to draw an oval that fits inside an invisible rectangle.  If the width and the height of the rectangle are the same, the result is a circle.

MIDP 2.0 doesn't even provide a drawOval method.  With MIDP 2.0, if you need a circle, you can construct it by drawing a closed circular arc.  (There are also other ways to draw a circle with MIDP 2.0, such as drawing a special case of a rounded rectangle.)

Draw a closed red circular arc on the off-screen Image

Listing 15 calls the drawArc method of the Image class to draw a red circle around the red ball image with a diameter that is twice the width of the image as shown in Figure 2.

Listing 15. Draw a closed red circular arc on the off-screen Image.
        osg.drawArc(horizBorder - redBallWidth,
                    vertBorder - redBallHeight,
                    redBallWidth * 2,
                    redBallHeight * 2,
                    0,
                    360);

Note that it is necessary to draw a 360-degree arc to draw a circle.  Also note that the angles are given in degrees instead of radians.  Finally, note that if the width of your image is not the same as the height of your image, you won't draw a circle.  Rather, you will draw something resembling an ellipse or an oval.  (Sun refers to it as an elliptical arc.)

Draw the green rectangle and the blue rounded rectangle

Listing 16 draws the green rectangle and the blue rounded rectangle centered on the green and blue ball images shown in Figure 2.

Listing 16. Draw the green rectangle and the blue rounded rectangle.
        //Draw a green rectangle around the green ball
        // with the width and height of the rectangle
        // being twice the width and height of the image.
        osg.setColor(0x00ff00);
        osg.drawRect(
                frameWidth - horizBorder - greenBallWidth,
                vertBorder - greenBallHeight,
                greenBallWidth * 2,
                greenBallHeight * 2);
                     
        //Draw a blue rounded rectangle around the blue
        // ball with the width and height of the rectangle
        // being twice the width and height of the image.
        osg.setColor(0x0000ff);
        osg.drawRoundRect(
                frameWidth/2 - blueBallWidth,
                frameHeight - vertBorder - blueBallHeight,
                blueBallWidth * 2,
                blueBallHeight * 2,
                (int)(1.25 * blueBallWidth),
                (int)(1.25 * blueBallHeight));

The methods

An exercise for the student
See if you can figure out how to use the method named drawRoundRect to draw a circle.

By now, the process should be sufficiently familiar to you that no explanation beyond the embedded comments in Listing 16 is necessary.  However, you might want to take a hard look at the documentation for the drawRoundRect method, particularly with regard to how the curvature of the corners is achieved.

Draw the off screen image onto the Canvas

Listing 17 calls the drawImage method of the Graphics class to draw the off-screen Image onto the Canvas.

Listing 17. Draw the off screen image onto the Canvas.
        g.drawImage(osi,
                    horizDrawPoint,
                    vertDrawPoint,
                    Graphics.HCENTER | Graphics.VCENTER);
      
      } catch(Exception e){e.printStackTrace();}

    }//end overridden paint method

This is the same drawImage method that was called in Listing 13 to draw the three small images onto the off-screen Image.

Positioning

The off-screen Image is smaller than the Canvas.  The parameter values specify that the off-screen Image will be drawn centered on the coordinates given by horizDrawPoint and vertDrawPoint.  Recall that these coordinate values were initialized to specify a point at the center of the Canvas.  Therefore, the first time that the off-screen Image is drawn on the Canvas, it will be centered on the Canvas.

Simple animation

However, you will see shortly that when the user presses the arrow keys on the cell phone emulator, the values stored in horizDrawPoint and vertDrawPoint are modified and the overridden paint method is called.  This causes the off-screen Image to be drawn at a different location on the Canvas and to appear at a different location on the cell phone emulator screen.

Beginning of the overridden keyPressed method

Listing 17 signals the end of the overridden paint method.  Listing 18 shows the beginning of the overridden keyPressed method.

Listing 18. Beginning of the overridden keyPressed method.
    public void keyPressed(int keyCode){
      String keyName = getKeyName(keyCode);
      if(keyName.equals("LEFT")){
        horizDrawPoint -= 5;
      }else if(keyName.equals("RIGHT")){
        horizDrawPoint += 5;
      }else if(keyName.equals("UP")){
        vertDrawPoint -= 5;
      }else if(keyName.equals("DOWN")){
        vertDrawPoint += 5;
      }else if(keyName.equals("SELECT")){
        //Reset to the center.
        horizDrawPoint = getWidth()/2;
        vertDrawPoint = getHeight()/2;
      }else{
        //Do nothing if the user presses a different key.
      }//end else

Override the keyPressed method

Listing 18 overrides the keyPressed method to implement a simple animation capability.  Pressing the arrow keys moves the image in the direction of the arrow.  Pressing the SELECT key resets the image to the center of the screen.

Note that pressing the UP arrow actually moves the image up the screen instead of down the screen.  Also note that this code was not designed to be portable.  It may only be compatible with the Sun cell phone emulator due to the use of specific key names that may be peculiar to the Sun cell phone emulator.

Listing 18 uses an if-else construct to adjust the values of the variables named horizDrawPoint and vertDrawPoint depending on whether the user presses one of the arrow keys, or presses the select key, (which is surrounded by the arrow keys in Figure 2).  The else clause in Listing 18 is superfluous.  I included it for clarity.

Repaint the screen with the image in the new position

Having (possibly) modified the values stored in the variables named horizDrawPoint and vertDrawPoint, Listing 19 calls the repaint method on the Canvas object (multiple presses of the SELECT key changes the values stored in the variables only once, if at all).

Listing 19. Repaint the screen with the image in the new position.
      this.repaint();
    }//end keyPressed
  }//end member class MyCanvas

}//end class Canvas03

The call to the repaint method sends a message to the AMS requesting that the overridden paint method (that began in Listing 6) be called.  Among other things, the overridden paint method clears the Canvas by painting it white and then draws the off-screen image on the canvas at the location specified by the values stored in the variables named horizDrawPoint and vertDrawPoint.  If the value stored in either of the variables has changed, the large rectangle and its contents shown in Figure 2 moves accordingly.

Listing 19 also signals the end of the Canvas03 class definition.

The MIDlet named Canvas04

Making the MIDlet more efficient

This is an update to the MIDlet named Canvas03.  This MIDlet contains several modifications to improve the efficiency of the MIDlet insofar as the simple animation is concerned without changing the behavior of the MIDlet.  The modifications are:

Efficiency isn't particularly important for this demonstration MIDlet, but it could be very important for a real MIDlet that is more computationally demanding.  Therefore, you need to think in terms of efficiency, particularly when animation is involved.

Will discuss in fragments

A complete listing of this MIDlet is provided in Listing 25.  Much of the code is identical to the code in the MIDlet named Canvas03.  Therefore, I won't repeat the explanation of that code.  Instead, I will explain only the code that has changed.

Move the off-screen image-generation code

In the MIDlet named Canvas03, the code that creates and populates the off-screen image is located in the overridden paint method.  This would be a good location for that code if the contents of the off-screen image changes while the MIDlet is running.  However, that isn't the case for this MIDlet.  Once the off-screen image for this MIDlet is created, it never changes.  Because that code is in the overridden paint method, it is executed every time the user pressed an arrow key.

The modified paint method

In the MIDlet named Canvas04, the code that creates and populates the off-screen image has been moved (with very few modifications) to the constructor for the Canvas class.  (You can view the modified constructor in Listing 25.)  As a result, that code will only be executed once when the Canvas object is instantiated.  This leaves the overridden paint method shown in Listing 20, which is short and much more efficient than the overridden paint method in Canvas03.

Listing 20. Overridden paint method in the MIDlet named Canvas04.
    public void paint(Graphics g){
      //Paint the screen white
      g.setColor(0xffffff);
      g.fillRect(0,0,getWidth(),getHeight());
      
      g.setColor(0x000000);//Set drawing color to black
      
      //Draw off-screen image on the Canvas
      g.drawImage(osi,
                  horizDrawPoint,
                  vertDrawPoint,
                  Graphics.HCENTER | Graphics.VCENTER);

    }//end overridden paint method

As you will see shortly, the next modification causes the execution of the overridden paint method to be even more efficient.

Paint only the portion of the screen that has changed

Two overloaded versions of the repaint method

The last statement in Listing 19, which is the last statement in the MIDlet named Canvas03, is a call to the method named repaint(Note the empty parameter list in the call to the repaint method in Listing 19.)  As of MIDP 2.0, there are two overloaded versions of the repaint method.  One is the version that is called in Listing 19, which causes the entire Canvas to be repainted.

The other version requires four parameters.  These parameters specify a rectangular portion of the Canvas.  When this version of the repaint method is called, only that portion of the Canvas contained within the specified rectangle is repainted.  Because the painting of the Canvas (and hence the screen) is a high-overhead process, limiting the area that is repainted can significantly improve the efficiency of a MIDlet.

No changes to the overridden paint method are required

It is important to note that the use of the second version of the repaint method to paint only a rectangular portion of the Canvas doesn't require any changes to the overridden paint method.  The reduction in the screen area that is repainted takes place behind the scenes at the system level.  The overridden paint method shown in Listing 20 will do the job regardless of which version of the repaint method is called.

Modifications to the overridden keyPressed method

The remaining modifications were made to the overridden keyPressed method.  As before, the keyPressed method is overridden to implement a simple animation capability.  Pressing the arrow keys moves the image in the direction of the arrow, and pressing the SELECT key resets the image to the center of the screen.  Also as before, this code is probably not portable and may only be compatible with the Sun cell phone emulator due to the use of specific key names.

Beginning of the overridden keyPressed method

The beginning of the modified keyPressed method is shown in Listing 21.  To understand the differences, compare the code in Listing 21 with the code in Listing 18 and Listing 19.

Listing 21. Beginning of the overridden keyPressed method.
    public void keyPressed(int keyCode){
      int step = 5;//Distance moved on each click.
      String keyName = getKeyName(keyCode);
      
      if(keyName.equals("LEFT")){
        horizDrawPoint -= step;
        //Repaint only the portion of the screen that 
        // needs to change.
        this.repaint(horizDrawPoint - frameWidth/2,
                     vertDrawPoint - frameHeight/2,
                     frameWidth + step,
                     frameHeight);
                     
        //Set multipleSelects to false to cause the SELECT
        // key to repaint the screen the next time it is
        // pressed..
        multipleSelects = false;

A different repaint call for each arrow key

In the MIDlet named Canvas03, the call to the repaint method is deferred until the end of the method.  The call is made with no parameters regardless of whether the user presses an arrow key or the SELECT key.  This causes the entire screen to be repainted each time the user presses one of those five keys.

However, in the MIDlet named Canvas04, a different call to the repaint method is associated with each arrow key and the SELECT key.  Furthermore, each call to the repaint method contains different parameter values depending on which key is pressed.

The rectangular repaint area
I will leave it as an exercise for the student to decipher the construction of the four parameters in Listing 21 to determine how they specify the rectangular portion of the screen that will be repainted.

The code in Listing 21 tests to see if the user pressed the LEFT arrow key.  If so, a call is made to the overloaded version of the repaint method that requires parameter to specify a rectangular area of the screen that is to be repainted.

Set multipleSelects to false

When control returns from the repaint method, the code in Listing 21 sets the value of a new variable named multipleSelects to false.  I will explain the reason for this later.

The remaining arrow keystroke tests in Canvas04

Listing 22 shows the tests for the RIGHT, UP, and DOWN arrow keys.

Listing 22. The remaining arrow keystroke tests in Canvas04.
      }else if(keyName.equals("RIGHT")){
        horizDrawPoint += step;
        this.repaint(horizDrawPoint - frameWidth/2 - step,
                     vertDrawPoint - frameHeight/2,
                     frameWidth + step,
                     frameHeight);
        multipleSelects = false;
      }else if(keyName.equals("UP")){
        vertDrawPoint -= step;
        this.repaint(horizDrawPoint - frameWidth/2,
                     vertDrawPoint - frameHeight/2,
                     frameWidth,
                     frameHeight + step);
        multipleSelects = false;
      }else if(keyName.equals("DOWN")){
        vertDrawPoint += step;
        this.repaint(horizDrawPoint - frameWidth/2,
                     vertDrawPoint - frameHeight/2 - step,
                     frameWidth,
                     frameHeight + step);
        multipleSelects = false;

The logic for each of these tests is the same as the logic in Listing 21.  However, a different rectangular area of the Canvas is identified for repainting in each case.

Eliminate multiple repainting on the SELECT key

When the user presses the SELECT key, the image is moved to the center of the screen.  In the MIDlet named Canvas03, if the user pressed the SELECT key again without pressing an arrow key, the entire Canvas is repainted.  Repainting the Canvas on repetitive presses of the SELECT key is wasteful because the contents of the Canvas don't change after the first time the SELECT key is pressed.

Code to eliminate wasteful repainting of the screen

Listing 23 contains the code that eliminates the wasteful repainting of the Canvas (and hence the screen) on repetitive presses of the SELECT key. 

Listing 23. Code to eliminate wasteful repainting of the screen.
      }else if(keyName.equals("SELECT")){
        //Reset to the center.
        horizDrawPoint = getWidth()/2;
        vertDrawPoint = getHeight()/2;
        if(!multipleSelects){
          //If the user presses SELECT two or more times
          // in succession, the screen will only be
          // repainted the first time.
          //Repaint the entire screen with the off-screen
          // image centered on the screen.
          this.repaint();
          multipleSelects = true;
        }//end if
      }else{
        //Do nothing if the user presses a different key.
      }//end else

    }//end keyPressed

A new variable named multipleSelects

If you examine the code for the class named MyCanvas in Listing 25, you will find the declaration and initialization of a new boolean variable named multipleSelects.  The variable is initialized to a value of false.  The value stored in this variable toggles between true and false depending on whether or not the screen should be repainted when the user presses the SELECT key.

Pressing the arrow keys sets the variable to false

As you saw in Listing 22, whenever the user presses an arrow key, the value of multipleSelects is set to false indicating that the screen should be repainted the next time the user presses the SELECT key.

Pressing SELECT sets the variable to true

As you can see in Listing 23, when the user presses the SELECT key, the entire Canvas is repainted once and then the value of multipleSelects is set to true.  This prevents Canvas from being repainted again if the user presses the SELECT key again without pressing one of the arrow keys.

Listing 23 also signals the end of the overridden keyPressed method and the end of the discussion of the MIDlet named Canvas04.

Run the programs

I encourage you to copy the MIDlet code from Listing 24 and Listing 25.  Run the MIDlets in the updated MIDlet development framework named WTKFramework03 that I provided in the lesson titled Using Alerts, Images, Timers, and Gauges in MIDlets (see Resources).  Experiment with the MIDlet code, making changes and running your modified MIDlets in the framework program.  See if you can explain the results produced by your changes.

Don't forget that you will need to download and install the latest version of the Sun Java Wireless Toolkit for CLDC (see Resources).  As of the date this lesson is being written, the latest version of the toolkit is WTK2.5.2.

If you run either of these MIDlets, you will need to provide three small image files to replace the image files in the above list.  Be sure to copy those files into the same directory as the directory that contains your source code, and either change the names of your image files to match the names of my files, or change the code in Listing 6 to match the names of your image files.

Summary

I began by presenting and explaining the MIDlet program named Canvas03.  This MIDlet introduces three major programming concepts not covered in earlier lessons:

In addition, the MIDlet named Canvas03 illustrates a variety of drawing capabilities of the Graphics class including:

Following that, I presented and explained an updated version of the MIDlet named Canvas04 that is more efficient during the animation process.

Resources

Complete program listings

Complete listings of the programs discussed in this lesson are shown in Listing 24 and Listing 25.

Listing 24. Source code for the MIDlet named Canvas03.
/*Canvas03.java
Copyright 2007, R.G.Baldwin

The purpose of this MIDlet is to illustrate the following
major programming concepts involving the use of the 
Canvas class and the Graphics class:

1. Creating an off-screen image.

2. Importing Image files and drawing them in specified 
locations on the off-screen image.

3. Drawing a variety of shapes in different colors on the
off-screen image, including the following:

  rectangle
  filled triangle
  arc (full circle)
  rounded rectangle

4. Drawing the off-screen image onto the Canvas in a 
specified location.

5. Handling keyPressed events on the Canvas to cause the 
off-screen image to be drawn at different locations on the
Canvas as a result of the user having pressed the 
following keys (simple animation):

  RIGHT
  LEFT
  UP
  DOWN
  SELECT

In addition, the MIDlet implements an EXIT command that 
can be used to terminate the MIDlet and cause it to enter
the destroyed state.

More detailed comments on the behavior of the program are
embedded in the program code.

Tested using a Java SE 6 compiler, targeted at a V1.4
virtual machine, and WTK 2.5.2 running under Windows XP.
*********************************************************/

package Canvas03;

import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Image;

public class Canvas03 extends MIDlet{
  Canvas myCanvas;

  public Canvas03(){
    System.out.println("Construct MIDlet");
    myCanvas = this.new MyCanvas();
    
    //Guarentee that the screen gets painted.
    myCanvas.repaint();
    
    //Add an EXIT command and register a CommandListener
    // to handle the EXIT command.
    myCanvas.addCommand(new Command("EXIT",
                                         Command.EXIT,2));
    myCanvas.setCommandListener(
      new CommandListener(){
        public void commandAction(
                          Command cmd,Displayable source){
          if(cmd.getCommandType() == Command.EXIT){
            destroyApp(true);
          }//end if
        }//end commandAction
      }//end new CommandListener
    );//end setCommandListener
  }//end constructor

  public void startApp(){
    //Make the Canvas the current display.
    Display.getDisplay(this).setCurrent(myCanvas);
  }//end startApp

  public void pauseApp(){
  }//end pauseApp

  public void destroyApp(boolean unconditional){
    System.out.println("Destroy MIDlet");
    notifyDestroyed();
  }//end destroyApp
  //----------------------------------------------------//
  
  //Member class
  class MyCanvas extends Canvas{
    Image redBallImage;
    Image greenBallImage;
    Image blueBallImage;
    int frameWidth = 200;
    int frameHeight = 200;
    int redBallWidth = 0;
    int redBallHeight = 0;
    int greenBallWidth = 0;
    int greenBallHeight = 0;
    int blueBallHeight = 0;
    int blueBallWidth = 0;
    int horizBorder = 50;
    int vertBorder = 50;
    int horizDrawPoint = 0;
    int vertDrawPoint = 0;
    
    MyCanvas(){//constructor
      //The following statement is superfluous because
      // false is the default. Set to true for full-screen
      // mode. Note: changing to true will hide the EXIT 
      // command.
      setFullScreenMode(false);
      horizDrawPoint = getWidth()/2;
      vertDrawPoint = getHeight()/2;
    }//end constructor

    public void paint(Graphics g){
      try{
        //Create three Image objects
        redBallImage = Image.createImage(
                                 "/Canvas03/redball.PNG");
        greenBallImage = Image.createImage(
                               "/Canvas03/greenball.PNG");
        blueBallImage = Image.createImage(
                                "/Canvas03/blueball.PNG");

        //Get and save the dimensions of the three images.
        redBallWidth = redBallImage.getWidth();
        redBallHeight = redBallImage.getHeight();
        greenBallWidth = greenBallImage.getWidth();
        greenBallHeight = greenBallImage.getHeight();
        blueBallWidth = blueBallImage.getWidth();
        blueBallHeight = blueBallImage.getHeight();
        
        //Paint the entire screen white.
        g.setColor(0xffffff);
        g.fillRect(0,0,getWidth(),getHeight());
        
        g.setColor(0x000000);//Set drawing color to black

        //Create an off screen image.
        Image osi = Image.createImage(frameWidth,
                                      frameHeight);
      
        //Get the Graphics object belonging to the off
        // screen image.
        Graphics osg = osi.getGraphics();

        //Draw a rectangle around the osg object. Note
        // the requirement to set the dimensions of the
        // rectangle to one less than the dimensions of
        // the osg to prevent the right and bottom sides
        // of the rectangle from falling off the edge of
        // the osg
        osg.drawRect(0,0,frameWidth-1,frameHeight-1);
        
        //Draw a filled triangle on the osg object.
        osg.setColor(0xa0a000);
        osg.fillTriangle(horizBorder,
                         vertBorder,
                         frameWidth - horizBorder,
                         vertBorder,
                         frameWidth/2,
                         frameHeight - vertBorder);

        // Draw the three images on the osg object with
        // each image centered on a vertex of the
        // triangle.
        osg.drawImage(redBallImage,
                      horizBorder,
                      vertBorder,
                      Graphics.HCENTER|Graphics.VCENTER);
        osg.drawImage(greenBallImage,
                      frameWidth - horizBorder,
                      vertBorder,
                      Graphics.HCENTER|Graphics.VCENTER);
        osg.drawImage(blueBallImage,
                      frameWidth/2,
                      frameHeight - vertBorder,
                      Graphics.HCENTER|Graphics.VCENTER);

        //Draw a red rectangle around the red ball image
        // with the width and height of the rectangle
        // being twice the width and height of the image.
        osg.setColor(0xff0000);
        osg.drawRect(horizBorder - redBallWidth,
                     vertBorder - redBallHeight,
                     redBallWidth * 2,
                     redBallHeight * 2);
       
        //Draw a red circle around the red ball image with
        // a diameter that is twice the width of the
        // image. Note that in MIDP 2.0, it is necessary
        // to draw a 360-degree arc to draw a circle.
        // Also note that the angles are given in degrees
        // instead of radians.
        osg.drawArc(horizBorder - redBallWidth,
                    vertBorder - redBallHeight,
                    redBallWidth * 2,
                    redBallHeight * 2,
                    0,
                    360);
        
        //Draw a green rectangle around the green ball
        // with the width and height of the rectangle
        // being twice the width and height of the image.
        osg.setColor(0x00ff00);
        osg.drawRect(
                frameWidth - horizBorder - greenBallWidth,
                vertBorder - greenBallHeight,
                greenBallWidth * 2,
                greenBallHeight * 2);
                     
        //Draw a blue rounded rectangle around the blue
        // ball with the width and height of the rectangle
        // being twice the width and height of the image.
        osg.setColor(0x0000ff);
        osg.drawRoundRect(
                frameWidth/2 - blueBallWidth,
                frameHeight - vertBorder - blueBallHeight,
                blueBallWidth * 2,
                blueBallHeight * 2,
                (int)(1.25 * blueBallWidth),
                (int)(1.25 * blueBallHeight));
        
        
      
        //Draw the off screen image onto the Canvas.
        // Center the image on the coordinates given by
        // the first two parameters.
        g.drawImage(osi,
                    horizDrawPoint,
                    vertDrawPoint,
                    Graphics.HCENTER | Graphics.VCENTER);
      
      } catch(Exception e){e.printStackTrace();}

    }//end overridden paint method
    //--------------------------------------------------//
    
    //Override the keyPressed method to implement a crude
    // animation capability. Pressing the arrow keys moves
    // the image in the direction of the arrow.  Pressing
    // the SELECT key resets the image to the center of
    // the screen. Note that pressing the UP arrow
    // actually moves the image up the screen instead of
    // down the screen
    //This code may only be compatible with the Sun cell
    // phone emulator due to the use of specific key
    // names.
    public void keyPressed(int keyCode){
      String keyName = getKeyName(keyCode);
      if(keyName.equals("LEFT")){
        horizDrawPoint -= 5;
      }else if(keyName.equals("RIGHT")){
        horizDrawPoint += 5;
      }else if(keyName.equals("UP")){
        vertDrawPoint -= 5;
      }else if(keyName.equals("DOWN")){
        vertDrawPoint += 5;
      }else if(keyName.equals("SELECT")){
        //Reset to the center.
        horizDrawPoint = getWidth()/2;
        vertDrawPoint = getHeight()/2;
      }else{
        //Do nothing if the user presses a different key.
      }//end else
      
      //Repaint the screen with the image in the new
      // position.
      this.repaint();
    }//end keyPressed
  }//end member class MyCanvas

}//end class Canvas03

 

Listing 25. Source code for the MIDlet named Canvas04.
/*Canvas04.java
Copyright 2007, R.G.Baldwin

This is an update to the MIDlet named Canvas03 to improve 
the efficiency of the MIDlet insofar as the simple 
animation is concerned.

The updates were:
1. Move the creation of the off-screen image to the
   constructor for the Canvas class so that it only
   needs to be executed once.
2. Paint only that portion of the screen that changes
   each time the black rectangle and its contents move..
3. Eliminate repainting the screen on multiple presses
   of the SELECT key.
   
The following comments were not modified when Canvas03
was updated to Canvas04.

The purpose of this MIDlet is to illustrate the following
major programming concepts involving the use of the 
Canvas class and the Graphics class:

1. Creating an off-screen image.

2. Importing Image files and drawing them in specified 
locations on the off-screen image.

3. Drawing a variety of shapes in different colors on the
off-screen image, including the following:

  rectangle
  filled triangle
  arc (full circle)
  rounded rectangle

4. Drawing the off-screen image onto the Canvas in a 
specified location.

5. Handling keyPressed events on the Canvas to cause the 
off-screen image to be drawn at different locations on the
Canvas as a result of the user having pressed the 
following keys (simple animation):

  RIGHT
  LEFT
  UP
  DOWN
  SELECT

In addition, the MIDlet implements an EXIT command that 
can be used to terminate the MIDlet and cause it to enter
the destroyed state.

More detailed comments on the behavior of the program are
embedded in the program code.

Tested using a Java SE 6 compiler, targeted at a V1.4
virtual machine, and WTK 2.5.2 running under Windows XP.
*********************************************************/

package Canvas04;

import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Image;

public class Canvas04 extends MIDlet{
  Canvas myCanvas;

  public Canvas04(){
    System.out.println("Construct MIDlet");
    myCanvas = this.new MyCanvas();
    
    //Guarentee that the screen gets painted.
    myCanvas.repaint();
    
    //Add an EXIT command and register a CommandListener
    // to handle the EXIT command.
    myCanvas.addCommand(new Command("EXIT",
                                         Command.EXIT,2));
    myCanvas.setCommandListener(
      new CommandListener(){
        public void commandAction(
                          Command cmd,Displayable source){
          if(cmd.getCommandType() == Command.EXIT){
            destroyApp(true);
          }//end if
        }//end commandAction
      }//end new CommandListener
    );//end setCommandListener
    
  }//end constructor

  public void startApp(){
    //Make the Canvas the current display.
    Display.getDisplay(this).setCurrent(myCanvas);
  }//end startApp

  public void pauseApp(){
  }//end pauseApp

  public void destroyApp(boolean unconditional){
    System.out.println("Destroy MIDlet");
    notifyDestroyed();
  }//end destroyApp
  //----------------------------------------------------//
  
  //Member class
  class MyCanvas extends Canvas{
    Image redBallImage;
    Image greenBallImage;
    Image blueBallImage;
    int frameWidth = 200;
    int frameHeight = 200;
    int redBallWidth = 0;
    int redBallHeight = 0;
    int greenBallWidth = 0;
    int greenBallHeight = 0;
    int blueBallHeight = 0;
    int blueBallWidth = 0;
    int horizBorder = 50;
    int vertBorder = 50;
    int horizDrawPoint = 0;
    int vertDrawPoint = 0;
    Image osi;
    boolean multipleSelects = false;//new to the update
    
    MyCanvas(){//constructor
      try{
        //The following statement is superfluous because
        // false is the default. Set to true for
        // full-screen mode. Note: changing to true will
        // hide the EXIT command.
        setFullScreenMode(false);
        horizDrawPoint = getWidth()/2;
        vertDrawPoint = getHeight()/2;
        
        //Create the off-screen image one time only.
        //Create three Image objects
        redBallImage = Image.createImage(
                                 "/Canvas04/redball.PNG");
        greenBallImage = Image.createImage(
                               "/Canvas04/greenball.PNG");
        blueBallImage = Image.createImage(
                                "/Canvas04/blueball.PNG");

        //Get and save the dimensions of the three images.
        redBallWidth = redBallImage.getWidth();
        redBallHeight = redBallImage.getHeight();
        greenBallWidth = greenBallImage.getWidth();
        greenBallHeight = greenBallImage.getHeight();
        blueBallWidth = blueBallImage.getWidth();
        blueBallHeight = blueBallImage.getHeight();

        //Create an off screen image.
        osi = Image.createImage(frameWidth,frameHeight);
      
        //Get the Graphics object belonging to the off-
        // screen image.
        Graphics osg = osi.getGraphics();

        //Draw a rectangle around the osg object. Note
        // the requirement to set the dimensions of the
        // rectangle to one less than the dimensions of
        // the osg to prevent the right and bottom sides
        // of the rectangle from falling off the edge of
        // the osg
        osg.drawRect(0,0,frameWidth-1,frameHeight-1);
        
        //Draw a filled triangle on the osg object.
        osg.setColor(0xa0a000);
        osg.fillTriangle(horizBorder,
                         vertBorder,
                         frameWidth - horizBorder,
                         vertBorder,
                         frameWidth/2,
                         frameHeight - vertBorder);

        // Draw the three images on the osg object with
        // each image centered on a vertex of the
        // triangle.
        osg.drawImage(redBallImage,
                      horizBorder,
                      vertBorder,
                      Graphics.HCENTER|Graphics.VCENTER);
        osg.drawImage(greenBallImage,
                      frameWidth - horizBorder,
                      vertBorder,
                      Graphics.HCENTER|Graphics.VCENTER);
        osg.drawImage(blueBallImage,
                      frameWidth/2,
                      frameHeight - vertBorder,
                      Graphics.HCENTER|Graphics.VCENTER);

        //Draw a red rectangle around the red ball image
        // with the width and height of the rectangle
        // being twice the width and height of the image.
        osg.setColor(0xff0000);
        osg.drawRect(horizBorder - redBallWidth,
                     vertBorder - redBallHeight,
                     redBallWidth * 2,
                     redBallHeight * 2);
       
        //Draw a red circle around the red ball image with
        // a diameter that is twice the width of the
        // image. Note that in MIDP 2.0, it is necessary
        // to draw a 360-degree arc to draw a circle.
        // Also note that the angles are given in degrees
        // instead of radians.
        osg.drawArc(horizBorder - redBallWidth,
                    vertBorder - redBallHeight,
                    redBallWidth * 2,
                    redBallHeight * 2,
                    0,
                    360);
        
        //Draw a green rectangle around the green ball
        // with the width and height of the rectangle
        // being twice the width and height of the image.
        osg.setColor(0x00ff00);
        osg.drawRect(
                frameWidth - horizBorder - greenBallWidth,
                vertBorder - greenBallHeight,
                greenBallWidth * 2,
                greenBallHeight * 2);
                     
        //Draw a blue rounded rectangle around the blue
        // ball with the width and height of the rectangle
        // being twice the width and height of the image.
        osg.setColor(0x0000ff);
        osg.drawRoundRect(
                frameWidth/2 - blueBallWidth,
                frameHeight - vertBorder - blueBallHeight,
                blueBallWidth * 2,
                blueBallHeight * 2,
                (int)(1.25 * blueBallWidth),
                (int)(1.25 * blueBallHeight));
      
      } catch(Exception e){e.printStackTrace();}
      
    }//end constructor
    //--------------------------------------------------//

    public void paint(Graphics g){
      
      //Paint the screen white. Note, only that portion of
      // the screen specified in the call to the repaint
      // method actually gets painted if the overloaded
      // version of the repaint method that requires
      // parameters is called. However, it is not
      // necessary for the paint method to take this into
      // account.
      g.setColor(0xffffff);
      g.fillRect(0,0,getWidth(),getHeight());
      
      g.setColor(0x000000);//Set drawing color to black
    
      //Draw the off screen image onto the Canvas.
      // Center the image on the coordinates given by
      // the first two parameters.
      g.drawImage(osi,
                  horizDrawPoint,
                  vertDrawPoint,
                  Graphics.HCENTER | Graphics.VCENTER);

    }//end overridden paint method
    //--------------------------------------------------//
    
    //Override the keyPressed method to implement a simple
    // animation capability. Pressing the arrow keys moves
    // the image in the direction of the arrow.  Pressing
    // the SELECT key resets the image to the center of
    // the screen. Note that pressing the UP arrow
    // actually moves the image up the screen instead of
    // down the screen
    //This code may only be compatible with the Sun cell
    // phone emulator due to the use of specific key
    // names.
    public void keyPressed(int keyCode){
      int step = 5;//Distance moved on each click.
      String keyName = getKeyName(keyCode);
      
      if(keyName.equals("LEFT")){
        horizDrawPoint -= step;
        //Repaint only the portion of the screen that 
        // needs to change.
        this.repaint(horizDrawPoint - frameWidth/2,
                     vertDrawPoint - frameHeight/2,
                     frameWidth + step,
                     frameHeight);
        //Set multipleSelects to false to cause the SELECT
        // key to repaint the screen the next time it is
        // pressed..
        multipleSelects = false;
      }else if(keyName.equals("RIGHT")){
        horizDrawPoint += step;
        this.repaint(horizDrawPoint - frameWidth/2 - step,
                     vertDrawPoint - frameHeight/2,
                     frameWidth + step,
                     frameHeight);
        multipleSelects = false;
      }else if(keyName.equals("UP")){
        vertDrawPoint -= step;
        this.repaint(horizDrawPoint - frameWidth/2,
                     vertDrawPoint - frameHeight/2,
                     frameWidth,
                     frameHeight + step);
        multipleSelects = false;
      }else if(keyName.equals("DOWN")){
        vertDrawPoint += step;
        this.repaint(horizDrawPoint - frameWidth/2,
                     vertDrawPoint - frameHeight/2 - step,
                     frameWidth,
                     frameHeight + step);
        multipleSelects = false;
      }else if(keyName.equals("SELECT")){
        //Reset to the center.
        horizDrawPoint = getWidth()/2;
        vertDrawPoint = getHeight()/2;
        if(!multipleSelects){
          //If the user presses SELECT two or more times
          // in succession, the screen will only be
          // repainted the first time.
          //Repaint the entire screen with the off-screen
          // image centered on the screen.
          this.repaint();
          multipleSelects = true;
        }//end if
      }else{
        //Do nothing if the user presses a different key.
      }//end else

    }//end keyPressed
  }//end Member class MyCanvas

}//end class Canvas04
//======================================================//

 


Copyright

Copyright 2008, Richard G. Baldwin.  Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.

About the author

Richard Baldwin is a college professor (at Austin Community College in Austin, TX) and private consultant whose primary focus is a combination of Java, C#, and XML. In addition to the many platform and/or language independent benefits of Java and C# applications, he believes that a combination of Java, C#, and XML will become the primary driving force in the delivery of structured information on the Web.

Richard has participated in numerous consulting projects and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas.  He is the author of Baldwin's Programming Tutorials, which have gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine.

In addition to his programming expertise, Richard has many years of practical experience in Digital Signal Processing (DSP).  His first job after he earned his Bachelor's degree was doing DSP in the Seismic Research Department of Texas Instruments.  (TI is still a world leader in DSP.)  In the following years, he applied his programming and DSP expertise to other interesting areas including sonar and underwater acoustics.

Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.

Baldwin@DickBaldwin.com

Keywords
java J2ME MIDlet "cell phone emulator" "wireless toolkit" WTK MIDP CLDC Displayable TextBox Ticker TextField Alerts Images Timers Gauges Lists Forms Items String StringItem ImageItem ChoiceGroup DateField Command CommandListener ItemCommandListener Canvas Graphics "anchor point" keyPressed paint repaint

-end-