MIDP Programming with J2ME
December 26, 2002
Images
The Graphics class also provides a method for drawing images. As
shown in the final version of TeleTransfer application, Images
can be predefined and contained in the JAR file of the MIDlet. The only file
format that is mandatory for MIDP is the Portable Network Graphics (PNG) file
format. The PNG format has several advantages over other graphics formats; for
example, it is license free and supports true color images, including a full
transparency (alpha) channel. PNG images are always compressed with a loss-less
algorithm. The algorithm is identical to the algorithm used for JAR files, so
the MIDP implementation can save space by using the same algorithm for both
purposes.
An image can be loaded from the JAR file using the static method
Image.create (String name). The name parameter denotes the
filename of the image in the JAR file. Please note that this create()
method may throw an IOException.
The drawImage() method in Graphics requires an Image
object, the coordinates, and an integer denoting the alignment as parameters.
The alignment parameter is similar the alignment of drawString(), except
that the BASELINE constant is not supported. An additional alignment
constant available for images only is VCENTER, which forces the image
to be vertically centered relative to the given coordinates. Figure
3.16 shows the valid constant combinations and the corresponding anchor
points.
Figure 3.16 Alignment
constant combinations valid for images and the corresponding anchor points.
The following example first loads the image logo.png from the MIDlet
JAR file in the constructor, and then displays the image three times. One image
is drawn in the upper-left corner, one in the lower-right corner, and one in
the center of the display, as shown in Figure 3.17:
import java.io.*;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
class ImageDemoCanvas extends Canvas {
Image image;
public ImageDemoCanvas () {
try {
image = Image.createImage ("/logo.png");
}
catch (IOException e) {
throw new RuntimeException ("Unable to load Image: "+e);
}
}
public void paint (Graphics g) {
g.setGrayScale (255);
g.fillRect (0, 0, getWidth (), getHeight ());
g.drawImage (image, 0, 0, Graphics.TOP | Graphics.LEFT);
g.drawImage (image, getWidth () / 2, getHeight () / 2,
Graphics.HCENTER | Graphics.VCENTER);
g.drawImage (image, getWidth (), getHeight (),
Graphics.BOTTOM | Graphics.RIGHT);
}
}
Figure 3.17 Output
of the ImageDemo example.
Images can also be created at runtime from scratch. The static
method Image.create (int width, int height) creates a new dynamic image
of the given size. In contrast to images loaded from a JAR file, these images
are mutable. Mutable images can be modified by calling getGraphics ().
The Graphics object returned can be used for modifying the image with
all the methods provided by the Graphics class. Please note that images
loaded from a JAR file cannot be modified. However, it is possible to create a
mutable image, and then draw any other image in the mutable image.
By modifying the constructor of the previous example canvas as follows, the
image drawn in the paint() method is created and filled at runtime
instead of loading an image from the JAR file:
public ImageDemoCanvas () {
image = Image.createImage (10,10);
image.getGraphics ().fillArc (0,0,10,10,0, 360);
}
The disadvantage of mutable images is that they cannot be used in high-level
GUI elements since it is possible to modify them at any time, possibly leading
to inconsistent display of widgets. For that reason, another static create
method, createImage(Image image), is provided that creates an immutable
image from another image.
Interaction
Because the Canvas class is a subclass of Displayable, it
provides the same support for commands as the high-level screen classes. Here,
you will concentrate on the additional interaction possibilities the
Canvas class offers: direct key input and pointer support.
Please note that all input events and command notifications and the
paint() method are called serially. That means that the application
manager will call none of the methods until the previous event handling method
has returned. So all these methods should return quickly, or the user will be
unable to interact with the application. For longer tasks, a separate thread can
be started.
Key Input
For key input, the Canvas class provides three callback methods:
keyPressed(), keyReleased(), and keyRepeated(). As
the names suggest, keyPressed() is called when a key is pressed,
keyRepeated() is called when the user holds down the key for a longer
period of time, and keyReleased() is called when the user releases the
key.
All three callback methods provide an integer parameter, denoting the Unicode
character code assigned to the corresponding key. If a key has no Unicode
correspondence, the given integer is negative. MIDP defines the following
constant for the keys of a standard ITU-T keypad: KEY_NUM0,
KEY_NUM1, KEY_NUM2, KEY_NUM3, KEY_NUM4,
KEY_NUM5, KEY_NUM6, KEY_NUM7, KEY_NUM8,
KEY_NUM9, KEY_POUND, and KEY_STAR. Applications
should not rely on the presence of any additional key codes. In particular,
upper- and lowercase or characters generated by pressing a key multiple times
are not supported by low-level key events. A "name" assigned to the
key can be queried using the getKeyName() method.
Some keys may have an additional meaning in games. For this purpose, MIDP
provides the constants UP, DOWN, LEFT,
RIGHT, FIRE, GAME_A, GAME_B,
GAME_C, and GAME_D. The "game" meaning of a keypress
can be determined by calling the getGameAction() method. The mapping
from key codes to game actions is device dependent, so different keys may map to
the same game action on different devices. For example, some devices may have
separate cursor keys; others may map the number pad to four-way movement. Also,
several keys may be mapped to the same game code. The game code can be
translated back to a key code using the getKeyCode() method. This also
offers a way to get the name of the key assigned to a game action. For example,
the help screen of an application may display
"press "+getKeyName (getKeyCode (GAME_A))
instead of "press GAME_A".
The following canvas implementation shows the usage of the key event methods.
For each key pressed, repeated, or released, it shows the event type, character
and code, key name, and game action.
The first part of the implementation stores the event type and code in two
variables and schedules a repaint whenever a key event occurs:
import javax.microedition.lcdui.*;
class KeyDemoCanvas extends Canvas {
String eventType = "- Press any!";
int keyCode;
public void keyPressed (int keyCode) {
eventType = "pressed";
this.keyCode = keyCode;
repaint ();
}
public void keyReleased (int keyCode) {
eventType = "released";
this.keyCode = keyCode;
repaint ();
}
public void keyRepeated (int keyCode) {
eventType = "repeated";
this.keyCode = keyCode;
repaint ();
}
The second part prints all event properties available to the device screen.
For this purpose, you first implement an additional write() method that
helps the paint() method to identify the current y position on the
screen. This is necessary because drawText() does not advance to a new
line automatically. The write() method draws the string at the given y
position and returns the y position plus the line height of the current font, so
paint() knows where to draw the next line:
public int write (Graphics g, int y, String s) {
g.drawString (s, 0, y, Graphics.LEFT|Graphics.TOP);
return y + g.getFont ().getHeight ();
}
The paint() method analyzes the keyCode and prints the result
by calling the write() method defined previously, as shown in Figure
3.18:
public void paint (Graphics g) {
g.setGrayScale (255);
g.fillRect (0, 0, getWidth (), getHeight ());
g.setGrayScale (0);
int y = 0;
y = write (g, y, "Key "+ eventType);
if (keyCode == 0) return;
y = write (g, y, "Char/Code: "+ ((keyCode < 0) ? "N/A" : ""
+(char) keyCode) + "/" + keyCode);
y = write (g, y, "Name: "+getKeyName (keyCode));
String gameAction;
switch (getGameAction (keyCode)) {
case LEFT: gameAction = "LEFT"; break;
case RIGHT: gameAction = "RIGHT"; break;
case UP: gameAction = "UP"; break;
case DOWN: gameAction = "DOWN"; break;
case FIRE: gameAction = "FIRE"; break;
case GAME_A: gameAction = "GAME_A"; break;
case GAME_B: gameAction = "GAME_B"; break;
case GAME_C: gameAction = "GAME_C"; break;
case GAME_D: gameAction = "GAME_D"; break;
default: gameAction = "N/A";
}
write (g, y, "Action: "+gameAction);
}
}
Figure 3.18 Output
of the KeyDemo example when the "Fire" key was released.
Pointer
Events
For devices supporting a pointer device such as a stylus, touch screen, or
trackball, the Canvas class provides three notification methods:
pointerPressed(), pointerDragged(), and
pointerReleased(). These methods work similarly to the key event
methods, except that they provide two integer parameters, denoting the x and y
position of the pointer when the corresponding event occurs. (Please note that
pointer support is optional in MIDP, so the application should not rely on the
presence of a pointer. Such devices are uncommon for devices such as mobile
phones.) The following sample program demonstrates the usage of the three
methods:
import javax.microedition.lcdui.*;
class PointerDemoCanvas extends Canvas {
String eventType = "Press Pointer!";
int x;
int y;
public void pointerPressed (int x, int y) {
eventType = "Pointer Pressed";
this.x = x;
this.y = y;
repaint ();
}
public void pointerReleased (int x, int y) {
eventType = "Pointer Released";
this.x = x;
this.y = y;
repaint ();
}
public void pointerDragged (int x, int y) {
eventType = "Pointer Repeated";
this.x = x;
this.y = y;
repaint ();
}
public void paint (Graphics g) {
g.setGrayScale (255);
g.fillRect (0, 0, getWidth (), getHeight ());
g.setGrayScale (0);
g.drawString (eventType + " " +x +"/"+y,
0, 0, Graphics.TOP|Graphics.LEFT);
g.drawLine (x-4, y, x+4, y);
g.drawLine (x, y-4, x, y+4);
}
}