Low-Level API
In contrast to the high-level API, the low-level API allows full control of
the MID display at pixel level. For this purpose, the lcdui package
contains a special kind of screen called Canvas. The Canvas
itself does not provide any drawing methods, but it does provide a
paint() callback method similar to the paint() method in AWT
components. Whenever the program manager determines that it is necessary to draw
the content of the screen, the paint() callback method of
Canvas is called. The only parameter of the paint() method is
a Graphics object. In contrast to the lcdui high-level
classes, there are many parallels to AWT in the low-level API.
The Graphics object provides all the methods required for actually
drawing the content of the screen, such as drawLine() for drawing
lines, fillRect() for drawing a filled rectangular area or
drawstring() for drawing text strings.
In contrast to AWT, lcdui does not let you mix high-level and
low-level graphics. It is not possible to display high-level and low-level
components on the screen simultaneously.
The program manager knows that it must call the paint() method of
Canvas when the instance of Canvas is shown on the screen.
However, a repaint can also be triggered by the application at any time. By
calling the repaint() method of Canvas, the system is notified
that a repaint is necessary, and it will call the paint() method. The
call of the paint() method is not performed immediately; it may be
delayed until the control flow returns from the current event handling method.
The system may also collect several repaint requests before paint() is
actually called. This delay normally is not a problem, but when you're
doing animation, the safest way to trigger repaints is to use
Display.callSerially() or to request the repaint from a separate
Thread or TimerTask. Alternatively, the application can force
an immediate repaint by calling serviceRepaints(). (For more
information, see the section "Animation" at the end of this
chapter.)
The Canvas class also provides some input callback methods that are
called when the user presses or releases a key or touches the screen with the
stylus (if one is supported by the device).
Basic Drawing
Before we go into the details of user input or animation, we will start with
a small drawing example showing the concrete usage of the Canvas and
Graphics classes.
The example clears the screen by setting the color to white and filling a
rectangle the size of the screen, determined by calling getWidth() and
getHeight(). Then it draws a line from coordinates (0,0) to (100,200).
Finally, it draws a rectangle starting at (20,30), 30 pixels wide and 20 pixels
high:
import javax.microedition.lcdui.*;
class DrawingDemoCanvas extends Canvas {
public void paint (Graphics g) {
g.setGrayScale (255);
g.fillRect (0, 0, getWidth (), getHeight ());
g.setGrayScale (0);
g.drawLine (0, 0, 100, 200);
g.fillRect (20, 30, 30, 20);
}
}
As you can see in the example code, you create a custom class
DrawingDemoCanvas in order to fill the paint() method.
Actually, it is not possible to draw custom graphics without creating a new
class and implementing the paint() method.
In order to really see your Canvas implementation running, you still
need a corresponding MIDlet. Here's the missing code:
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
public class DrawingDemo extends MIDlet {
public void startApp () {
Display.getDisplay (this).setCurrent (new DrawingDemoCanvas ());
}
public void pauseApp () {}
public void destroyApp (boolean forced) {}
}
Now you can start your DrawingDemo MIDlet. Depending on the screen
size of the device, it will create output similar to Figure
3.9. In most subsequent examples, you will omit the MIDlet since it is basically
the same as this one, except that the name of your Canvas class will
be different.
Figure 3.9 Output
of the DrawingDemo MIDlet.
In the example, the screen is cleared before drawing because the
system relies on the paint() method to fill every pixel of the draw
region with a valid value. You don't erase the previous content of the
screen automatically because doing so may cause flickering of animations. The
application cannot make any assumptions about the content of the Screen
before paint() is called. The screen may be filled with the content
drawn at the last call of paint(), but it may also be filled with an
alert box remaining from an incoming phone call, for example.
Drawing Style and Color
In the DrawingDemoCanvas implementation, you can find two calls to
setGrayScale(). The setGrayScale() method sets the gray scale
value for the following drawing operations. Valid grayscale values range from 0
to 255, where 0 means black and 255 means white. Not all possible values may
actually render to different gray values on the screen. If the device provides
fewer than 256 shades of gray, the best fitting value supported by the device is
chosen. In the example, the value is first set to white, and the screen is
cleared by the following call to drawRect(). Then, the color is set to
black for the subsequent drawing operations.
The setGrayScale() method is not the only way to influence the color
of subsequent drawing. MIDP also provides a setColor() method. The
setColor() method has three parameters holding the red, green, and blue
components of the desired color. Again, the values range from 0 to 255, where
255 means brightest and 0 means darkest. If all three parameters are set to the
same value, the call is equivalent to a corresponding call of
setGrayScale(). If the device is not able to display the desired color,
it chooses the best fitting color or grayscale supported by the device
automatically. Some examples are listed in Table 3.7.
Table 3.7 Example Color Parameter Settings
|
Parameter Settings
|
Resulting Color
|
|
setColor (255, 0, 0)
|
Red
|
|
setColor (0, 255, 0)
|
Green
|
|
setColor (0, 0, 255)
|
Blue
|
|
setColor (128, 0, 0)
|
Dark red
|
|
setColor (255, 255, 0)
|
Yellow
|
|
setColor (0, 0, 0)
|
Black
|
|
setColor (255, 255, 255)
|
White
|
|
setColor (128, 128, 128)
|
50% gray
|
The only other method that influences the current style of
drawing is the setStrokeStyle() method. The setStrokeStyle()
command sets the drawing style of lines to dotted or solid. You determine the
style by setting the parameter to one of the constants DOTTED or
SOLID, defined in the Graphics class.
When the paint() method is entered, the initial drawing color is
always set to black and the line style is SOLID.
Simple Drawing Methods
In the example, you have already seen fillRect() and
drawLine(). Table 3.8 shows all drawing primitives contained in the
Graphics class. All operations where the method names begin with
draw, except drawstring() and drawImage(), are
influenced by the current color and line style. They draw the outline of a
figure, whereas the fill methods fill the corresponding area with the
current color and do not depend on the line style.
Table 3.8 Drawing Methods of the Graphics Class
|
Method
|
Purpose
|
|
drawImage (Image image,
|
Draws an Image. Explained in detail in the int x, int y, int align)
"Images" section.
|
|
drawString (String text,
|
Draws a text string at the given position in the int x, int y, int
align) current color; see "Text and Fonts."
|
|
drawRect (int x, int y,
|
Draws an empty rectangle with the upper-left int w, int h) corner
at the given (x,y)coordinate, with the given width and a height. The next
section explains why the rectangle is one pixel larger than you might
expect.
|
|
drawRoundRect (int x, int y,
|
Like drawRect(), except that an additional radius int w, int
h, int r) is given for rounded corners of the rectangle.
|
|
drawLine (int x0, int y0,
|
Draws a line from (x0,y0) to (x1,y1). int x1, int y1)
|
|
drawArc (int x, int y, Draws the outline of a circular or elliptical
arc int w, int h,
|
covering the specified rectangle, using the current int startAng, int
arcArc) color and stroke style. The resulting arc begins at
startAng and extends for arcAng degrees. Angles are
interpreted such that 0 degrees is at the 3 o'clock position. A positive
value indicates a counter-clockwise rotation while a negative value indicates a
clockwise rotation.
|
|
fillRect (int x, int y,
|
Similar to drawRect(), but fills the given area int w,
int h) with the current color.
|
|
fillRoundRect (int x, int y,
|
Related to fillRect() as drawRoundRect() is int w, int
h, related to drawRect(). int startAng, int endAng);
|
|
fillArc (int x, int y,
|
Like drawArc(), but fills the corresponding region. int w, int
h, int startAng, int endAng);
|
Coordinate System and Clipping
In the drawing example, we already have used screen coordinates without explaining
what they actually mean. You might know that the device display consists of
little picture elements (pixels). Each of these pixels is addressed by its position
on the screen, measured from the upper-left corner of the device, which is the
origin of the coordinate system. Figure 3.10 shows the lcdui coordinate system.
Actually, in Java the coordinates do not address the pixel itself, but the
space between two pixels, where the "drawing pen" hangs to the lower
right. For drawing lines, this does not make any difference, but for rectangles
and filled rectangles this results in a difference of one pixel in width and
height: In contrast to filled rectangles, rectangles become one pixel wider and
higher than you might expect. While this may be confusing at first glance, it
respects the mathematical notation that lines are infinitely thin and avoids
problems when extending the coordinate system to real distance measures, as in
the J2SE class Graphics2D.
Figure 3.10 The lcdui
coordinate system.
In all drawing methods, the first coordinate (x) denotes the
horizontal distance from the origin and the second coordinate (y) denotes the
vertical distance. Positive coordinates mean a movement down and to the right.
Many drawing methods require additional width and height parameters. An
exception is the drawLine() method, which requires the absolute
coordinates of the destination point.
The origin of the coordinate system can be changed using the
translate() method. The given coordinates are added to all subsequent
drawing operations automatically. This may make sense if addressing coordinates
relative to the middle of the display is more convenient for some applications,
as shown in the section "Scaling and Fitting," later in the chapter.
The actual size of the accessible display area can be queried using the
getWidth() and getHeight() methods, as performed in the first
example that cleared the screen before drawing. The region of the screen where
drawing takes effect can be further limited to a rectangular area by the
clipRect() method. Drawing outside the clip area will have no effect.
The following example demonstrates the effects of the clipRect()
method. First, a dotted line is drawn diagonally over the display. Then a
clipping region is set. Finally, the same line as before is drawn using the
SOLID style:
import javax.microedition.lcdui.*;
class ClipDemoCanvas extends Canvas {
public void paint (Graphics g) {
g.setGrayScale (255);
g.fillRect (0, 0, getWidth (), getHeight ());
int m = Math.min (getWidth (), getHeight ());
g.setGrayScale (0);
g.setStrokeStyle (Graphics.DOTTED);
g.drawLine (0, 0, m, m);
g.setClip (m / 4, m / 4, m / 2, m / 2);
g.setStrokeStyle (Graphics.SOLID);
g.drawLine (0, 0, m, m);
}
}
Figure 3.11 shows the resulting image. Although
both lines have identical start and end points, only the part covered by the
clipping area is replaced by a solid line.
Figure 3.11 Output
of the clipRect() example: Only the part covered by the clipping area
is redrawn solid, although the line coordinates are identical.
When the paint() method is called from
the system, a clip area may already be set. This may be the case if the
application just requested repainting of a limited area using the parameterized
repaint call, or if the device just invalidated a limited area of the display,
for example if a pop-up dialog indicating an incoming call was displayed but did
not cover the whole display area.
Actually, clipRect() does not set a new clipping area, but instead
shrinks the current clip area to the intersection with the given rectangle. In
order to enlarge the clip area, use the setClip() method.
The current clip area can be queried using the getClipX(),
getClipY(), getClipWidth(), and getClipHeight()
methods. When drawing is computationally expensive, this information can be
taken into account in order to redraw only the areas of the screen that need an
update.
Text and Fonts
For drawing text, lcdui provides the method drawstring().
In addition to the basic drawstring() method, several variants let
you draw partial strings or single characters. (Details about the additional
methods can be found in the lcdui API documentation.) The simple drawstring()
method takes four parameters: The character string to be displayed, the x and
y coordinates, and an integer determining the horizontal and vertical alignment
of the text. The alignment parameter lets you position the text relative to
any of the four corners of its invisible surrounding box. Additionally, the
text can be aligned to the text baseline and the horizontal center. The sum
or logical or (|) of a constant for horizontal alignment (LEFT,
RIGHT, and HCENTER) and constants for vertical alignment (TOP,
BOTTOM, and BASELINE) determine the actual alignment. Figure
3.12 shows the anchor points for the valid constant combinations.
Figure 3.12 Valid
combinations of the alignment constants and the corresponding anchor points.
The following example
illustrates the usage of the drawstring() method. By choosing the
anchor point correspondingly, the text is displayed relative to the upper-left
and lower-right corner of the screen without overlapping the screen border:
import javax.microedition.lcdui.*;
class TextDemoCanvas extends Canvas {
public void paint (Graphics g) {
g.setGrayScale (255);
g.fillRect (0, 0, getWidth (), getHeight ());
g.setGrayScale (0);
g.drawString ("Top/Left", 0, 0, Graphics.TOP | Graphics.LEFT);
g.drawString ("Baseline/Center", getWidth () / 2, getHeight () / 2,
Graphics.HCENTER | Graphics.BASELINE);
g.drawString ("Bottom/Right", getWidth (), getHeight (),
Graphics.BOTTOM | Graphics.RIGHT);
}
}
Figure 3.13 shows the output of the TextDemo
example.
Figure 3.13 Output
of the TextDemo example.
In addition to the current drawing color, the result of the
drawstring() method is influenced by the current font. MIDP provides
support for three different fonts in three different sizes and with the three
different attributes: bold, italic, and underlined.
A font is not selected directly, but the setFont() method takes a
separate Font object, describing the desired font, as a parameter.
The explicit Font class provides additional information about the font,
such as its width and height in pixels, baseline position, ascent and descent,
and so on. Figure 3.14 illustrates the meaning
of the corresponding values. This information is important for operations such
as drawing boxes around text strings. In addition, word-wrapping algorithms
rely on the actual pixel width of character strings when rendered to the screen.
Figure 3.14 Font properties
and the corresponding query methods.
A Font object is created by calling the static method
createFont() of the class Font in the lcdui package.
The createFont() method takes three parameters: the font type, style,
and size of the font. Similar to the text alignment, there are predefined
constants for setting the corresponding value; these constants are listed in
Table 3.9.
Table 3.9 createFont() Property Constants
|
Property
|
Constants
|
|
Size
|
SIZE_SMALL, SIZE_MEDIUM, SIZE_LARGE
|
|
Style
|
STYLE_PLAIN, STYLE_ITALICS, STYLE_BOLD,
STYLE_UNDERLINED
|
|
Face
|
FACE_SYSTEM, FACE_MONOSPACE, FACE_PROPORTIONAL
|
The style constants can be combinedfor example,
STYLE_ITALICS | STYLE_BOLD will result in a bold italics font
style.
The following example shows a list of all fonts available, as far as the list
fits on the screen of the device:
import javax.microedition.lcdui.*;
class FontDemoCanvas extends Canvas {
static final int [] styles = {Font.STYLE_PLAIN,
Font.STYLE_BOLD,
Font.STYLE_ITALIC};
static final int [] sizes = {Font.SIZE_SMALL,
Font.SIZE_MEDIUM,
Font.SIZE_LARGE};
static final int [] faces = {Font.FACE_SYSTEM,
Font.FACE_MONOSPACE,
Font.FACE_PROPORTIONAL};
public void paint (Graphics g) {
Font font = null;
int y = 0;
g.setGrayScale (255);
g.fillRect (0, 0, getWidth (), getHeight ());
g.setGrayScale (0);
for (int size = 0; size < sizes.length; size++) {
for (int face = 0; face < faces.length; face++) {
int x = 0;
for (int style = 0; style < styles.length; style++) {
font = Font.getFont
(faces [face], styles [style], sizes [size]);
g.setFont (font);
g.drawString
("Test", x+1, y+1, Graphics.TOP | Graphics.LEFT);
g.drawRect
(x, y, font.stringWidth ("Test")+1,
font.getHeight () + 1);
x += font.stringWidth ("Test")+1;
}
y += font.getHeight () + 1;
}
}
}
}
Figure 3.15 shows the output of the FontDemo
example.
Figure 3.15 Output
of the FontDemo example.