Animation
With animation, there are normally two main problems: Display
flickering and synchronization of painting with calculation of new frames. We
will first address how to get the actual painting and application logic in sync,
and then solve possible flickering.
Synchronization of Frame Calculation and
Drawing
When you perform animations, you can first calculate the display content and
then call repaint() in order to paint the new frame. But how do you
know that the call to paint() has finished? One possibility would be to
call serviceRepaints(), which blocks until all pending display updates
are finished. The problem with serviceRepaints() is that
paint() may be called from another thread. If the thread calling
serviceRepaints() holds any locks that are required in
paint(), a deadlock may occur. Also, calling serviceRepaints()
makes sense only from a thread other than the event handling thread. Otherwise,
key events may be blocked until the animation is over. An alternative to
serviceRepaints() is calling callSerially() at the end of the
paint() method. The callSerially() method lets you put
Runnable objects in the event queue. The run() method of the
Runnable object is then executed serially like any other event handling
method. In the run() method, the next frame can be set up, and a new
repaint can be requested there.
To demonstrate this execution model, you will build a simple stopwatch that
counts down a given number of seconds by showing a corresponding pie slice using
the fillArc() method, as shown in Figure 3.21.
Figure 3.21 A very
simple stopwatch.
The
Canvas implementation stores the current slice in degree, the start
time, the total amount of seconds and the MIDlet display in local variables. In
order to make use of callSerially(), your Canvas implements
the Runnable interface:
class StopWatchCanvas extends Canvas implements Runnable {
int degree = 360;
long startTime;
int seconds;
Display display;
When the StopWatchCanvas is created, you store the given display and
seconds. Then, the current time is determined and stored, too:
StopWatchCanvas (Display display, int seconds) {
this.display = display;
this.seconds = seconds;
startTime = System.currentTimeMillis ();
}
In the paint() method, you clear the display. If you need to draw
more than 0 degrees, you fill a corresponding arc with red color and request
recalculation of the pie slice using callSerially(). Finally, you draw
the outline of the stopwatch by setting the color to black and calling
drawArc():
public void paint (Graphics g) {
g.setGrayScale (255);
g.fillRect (0, 0, getWidth (), getHeight ());
if (degree > 0) {
g.setColor (255, 0, 0);
g.fillArc (0,0, getWidth (), getHeight (), 90, degree);
display.callSerially (this);
}
g.setGrayScale (0);
g.drawArc (0, 0, getWidth ()-1, getHeight ()-1, 0, 360);
}
This method is invoked by the event handling thread as a result of the
previous display.callSerially(this) statement. In this case, it just
calculates a new pie slice and requests a repaint():
public void run () {
int permille = (int) ((System.currentTimeMillis ()
- startTime) / seconds);
degree = 360 - (permille * 360) / 1000;
repaint ();
}
}
As always, you need a MIDlet to actually display your
StopWatchCanvas implementation. The following code creates a stopwatch
set to 10 seconds when the application is started:
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
public class StopWatch extends MIDlet {
public void startApp () {
Display display = Display.getDisplay (this);
display.setCurrent (new StopWatchCanvas (display, 10));
}
public void pauseApp () {
}
public void destroyApp (boolean forced) {
}
}
Avoiding Flickering
On some devices, the stopwatch implementation will flicker. This is due to
the fact that the display is cleared completely before a new stopwatch is drawn.
However, on some other devices, the stopwatch will not flicker because those
devices provide automated double buffering. Before the screen is updated, all
drawing methods are performed in a hidden buffer area. Then, when the
paint() method is finished, the complete display is updated from the
offscreen buffer at once. The method isDoubleBuffered() in the
Canvas class is able to determine whether the device screen is double
buffered.
In order to avoid flickering of your animation in all cases, you can add your
own offscreen image, which is allocated only if the system does not provide
double buffering:
Image offscreen = isDoubleBuffered () ? null :
Image.createImage (getWidth (), getHeight ());
In the paint() method, you just check if the offscreen image is not
null, and if so, you delegate all drawing to your offscreen buffer. The
offscreen buffer is then drawn immediately at the end of the paint()
method, without first clearing the screen. Clearing the screen is not necessary
in that case since the offscreen buffer was cleared before drawing and it fills
every pixel of the display:
public void paint (Graphics g) {
Graphics g2 = offscreen == null ? g : offscreen.getGraphics ();
g2.setGrayScale (255);
g2.fillRect (0, 0, getWidth (), getHeight ());
if (degree > 0) {
g2.setColor (255, 0, 0);
g2.fillArc (0,0, getWidth (), getHeight (), 90, degree);
display.callSerially (this);
}
g2.setGrayScale (0);
g2.drawArc (0, 0, getWidth ()-1, getHeight ()-1, 0, 360);
if (offscreen != null)
g.drawImage (offscreen, 0, 0, Graphics.TOP | Graphics.RIGHT);
}
Listing 3.3 gives the complete source code for the buffered stopwatch.
Listing 3.3 BufferedStopWatch.java The Complete Source Code of
the Buffered Stopwatch
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
class BufferedStopWatchCanvas extends Canvas implements Runnable {
int degree = 360;
long startTime;
int seconds;
Display display;
Image offscreen;
BufferedStopWatchCanvas (Display display, int seconds) {
this.display = display;
this.seconds = seconds;
if (!isDoubleBuffered () && false)
offscreen = Image.createImage (getWidth (), getHeight ());
startTime = System.currentTimeMillis ();
}
public void paint (Graphics g) {
Graphics g2 = offscreen == null
? g
: offscreen.getGraphics ();
g2.setGrayScale (255);
g2.fillRect (0, 0, getWidth (), getHeight ());
if (degree > 0) {
g2.setColor (255, 0, 0);
g2.fillArc (0,0, getWidth (), getHeight (), 90, degree);
display.callSerially (this);
}
g2.setGrayScale (0);
g2.drawArc (0, 0, getWidth ()-1, getHeight ()-1, 0, 360);
if (offscreen != null)
g.drawImage (offscreen, 0, 0, Graphics.TOP | Graphics.RIGHT);
}
public void run () {
int permille = (int) ((System.currentTimeMillis ()
- startTime) / seconds);
degree = 360 - (permille * 360) / 1000;
repaint ();
}
}
public class BufferedStopWatch extends MIDlet {
public void startApp () {
Display display = Display.getDisplay (this);
display.setCurrent (new BufferedStopWatchCanvas (display, 10));
}
public void pauseApp () {
}
public void destroyApp (boolean forced) {
}
}
Summary
In this chapter, you learned the general life cycle of MIDP applications. You
know how to build a user interface using the high-level lcdui widgets,
and how to interact using the listener mechanism. You have learned to perform
custom graphics using the low-level API, including device-independent
flicker-free animation and coordination of graphics calculation and drawing.
The next chapter gives a corresponding overview of the PDAP life cycle and
user interface. The PDAP introduction focuses on the differences between the
J2SE AWT classes and the subset included in PDAP, but still gives a basic
introduction to AWT programming.
About the Authors
Michael Kroll and Stefan Haustein are the creators of the kAWT-Project, an abstract window toolkit designed to allow graphical J2ME programming on devices using the KVM. Since the first release of KVM, both have been members of the Expert group specifying the J2ME PDA Profile.
Michael Kroll's experience in professional J2ME programming includes an application for viewing biosignals like ECGs and EEGs on a Palm organizer. Michael is working for Visus Technology Transfer in Germany developing a Java based radiology imaging system called JiveX.
Stefan Haustein studied Computer Science at the University of Dortmund, and is working on his Ph.D in the AI-Unit. He wrote his diploma thesis in the Neuros-Project at the "Institut fur Neuroinformatik" at the University of Bochum about graph-based robot navigation.
Source of this material
 |
This is Chapter 3: MIDP Programming from the book J2ME Application Development (ISBN:0-672-323095-9) written by Michael Kroll and Stefan Haustein, published by Sams Publishing.
© Copyright Pearson Education. All rights reserved. |
To access the full Table of Contents for the book
Other Chapters from Sams Publishing:
Web Services and Flows (WSFL)
Overview of JXTA
Introduction to EJBs
Processing Speech with Java
The Java Database Control in BEA Weblogic