|
Download the source
On occasion you may want your MIDP application to display a counter, for
example to display the number of seconds elapsed since some operation
started. It's fairly simple to write a custom component that
automatically updates and displays such a counter. With a bit of careful
coding you can even make it work with canvases, custom application-drawn
screens derived from the Canvas class, and on forms,
using the MIDP 2.0 CustomItem class.
The key to making a dual-purpose component is to separate the drawing of
the component from its management. The reason is that there is no
concept of a component when working with a canvas as a whole; the
application itself must draw and manage the entire canvas. A custom
item, on the other hand, is a true component; the system delegates
drawing to the component but handles most of the management itself. To
handle this difference, you define a callback interface that your
component uses to delegate management tasks to the appropriate code.
Here's the core of a sample counter component, the CounterArea class:
import javax.microedition.lcdui.*;
// The counter class, which can be used on a canvas
// or wrapped within a custom item.
public class CounterArea {
public static final int DEFAULT_RATE = 500;
public static final int MIN_RATE = 100;
// The callback interface by which we notify
// the counter owner of certain events.
public interface Callback {
void invalidateCounter( CounterArea counter );
void repaintCounter( CounterArea counter );
void resizeCounter( CounterArea counter );
}
public CounterArea(){
}
public CounterArea( int width, int height ){
_width = width;
_height = height;
}
public int getBackColor(){
return _backColor;
}
public Callback getCallback(){
return _callback;
}
public Font getFont(){
return _font;
}
public Font getFontForDrawing(){
return _font != null ? _font :
Font.getDefaultFont();
}
public int getHeight(){
if( _height < 0 ){
_height = getMinHeight();
}
return _height;
}
public int getMinHeight(){
return getFontForDrawing().getHeight();
}
public int getMinWidth(){
Font f = getFontForDrawing();
return f.stringWidth( Integer.toString( _value ) );
}
public int getRate(){
return _rate;
}
public int getTextColor(){
return _textColor;
}
public int getValue(){
return _value;
}
public int getWidth(){
if( _width < 0 ){
_width = getMinWidth();
}
return _width;
}
private void invalidate(){
if( _callback != null ){
_callback.invalidateCounter( this );
}
}
public boolean isCounting(){
return _timer != null;
}
public void paint( Graphics g ){
String s = Integer.toString( _value );
Font f = getFontForDrawing();
int w = f.stringWidth( s );
int h = f.getHeight();
int aw = getWidth();
int ah = getHeight();
g.setColor( _backColor );
g.fillRect( _left, _top, aw, ah );
g.setColor( _textColor );
g.drawString( s, _left + aw - w,
_top + ( ah - h ) / 2,
g.TOP | g.LEFT );
if( w > aw || h > ah ){
resize();
}
}
private void repaint(){
if( _callback != null ){
_callback.repaintCounter( this );
}
}
private void resize(){
if( _callback != null ){
_callback.resizeCounter( this );
}
}
private synchronized boolean increment( Runnable source ){
if( source != _timer ) return false;
++_value;
repaint();
return true;
}
public void setBackColor( int color ){
_backColor = color;
invalidate();
}
public void setCallback( Callback callback ){
_callback = callback;
}
public void setLeft( int left ){
_left = left;
}
public void setFont( Font f ){
_font = f;
invalidate();
}
public void setHeight( int h ){
_height = h;
}
public void setRate( int rate ){
_rate = ( rate < MIN_RATE ? MIN_RATE : rate );
}
public void setTextColor( int color ){
_textColor = color;
invalidate();
}
public void setTop( int top ){
_top = top;
}
public synchronized void setValue( int value ){
_value = value;
}
public void setWidth( int w ){
_width = w;
}
public synchronized void start(){
_timer = new CounterTimer();
new Thread( _timer ).start();
}
public synchronized void stop(){
_timer = null;
}
private int _backColor = 0x00FFFFFF;
private Callback _callback;
private Font _font;
private int _height = -1;
private int _index;
private int _left;
private int _rate = DEFAULT_RATE;
private int _textColor = 0x00000000;
private Runnable _timer;
private int _top;
private int _width = -1;
private int _value;
//-------------------------------------------------
// A very simple timer that sleeps and wakes
// up at regular intervals.
private class CounterTimer implements Runnable {
public void run(){
Thread t = Thread.currentThread();
while( true ){
try {
t.sleep( _rate );
}
catch( InterruptedException e ){
}
if( !increment( this ) ) break;
}
}
}
}
|
The counter paints its value centered horizontally and vertically within
its drawing area and attempts to resize that area if its dimensions are
too small. It also defines a public nested interface,
CounterArea.Callback, to delegate the management tasks of invalidating,
repainting, and resizing the component. A private inner class,
CounterTimer, updates the counter value in the background.
The CounterArea class can be used directly on a canvas, but you can also
wrap it with a custom item. The wrapper class, CounterItem, looks like
this:
import javax.microedition.lcdui.*;
// A custom component for MIDP 2.0 that wraps
// a CounterArea instance.
public class CounterItem extends CustomItem
implements CounterArea.Callback {
public CounterItem(){
super( null );
_area = new CounterArea();
_area.setCallback( this );
}
public Font getFont(){
return _area.getFont();
}
public int getMinContentHeight(){
return _area.getMinHeight();
}
public int getMinContentWidth(){
return _area.getMinWidth();
}
public int getPrefContentHeight( int width ){
return getMinContentHeight();
}
public int getPrefContentWidth( int height ){
return getMinContentWidth();
}
public int getValue(){
return _area.getValue();
}
protected void hideNotify(){
_area.stop();
}
public void invalidateCounter( CounterArea counter )
if( counter == _area ){
invalidate();
}
}
protected void paint( Graphics g, int width, int height ){
_area.paint( g );
}
public void repaintCounter( CounterArea counter ){
if( counter == _area ){
repaint();
}
}
public void resizeCounter( CounterArea counter ){
if( counter == _area ){
invalidate();
}
}
protected void sizeChanged( int w, int h ){
_area.setWidth( w );
_area.setHeight( h );
}
public void setFont( Font f ){
_area.setFont( f );
}
public void setValue( int value ){
_area.setValue( value );
}
protected void showNotify(){
_area.start();
}
public boolean traverse( int dir, int vw, int vh,
int[] vrect ){
return false;
}
private CounterArea _area;
}
|
The wrapper does very little beyond acting as a sink for the management
callback and for automatically starting and stopping the counter
whenever the item is displayed or hidden. You might wish to change this
behavior, of course.
Finally, here's a simple test MIDlet that demonstrates the use of the
counter:
import java.io.*;
import java.util.*;
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
// A simple MIDlet to test the custom counter
// component.
public class CounterItemTest extends MIDlet
implements CommandListener {
private Display display;
public static final Command exitCommand =
new Command( "Exit",
Command.EXIT, 1 );
public CounterItemTest(){
}
public void commandAction( Command c,
Displayable d ){
if( c == exitCommand ){
exitMIDlet();
}
}
protected void destroyApp( boolean unconditional )
throws MIDletStateChangeException {
exitMIDlet();
}
public void exitMIDlet(){
notifyDestroyed();
}
public Display getDisplay(){ return display; }
protected void initMIDlet(){
Form f = new Form( "CounterItem Test" );
f.addCommand( exitCommand );
f.setCommandListener( this );
CounterItem counter = new CounterItem();
counter.setLayout( Item.LAYOUT_CENTER |
Item.LAYOUT_NEWLINE_BEFORE |
Item.LAYOUT_NEWLINE_AFTER );
f.append( counter );
getDisplay().setCurrent( f );
}
protected void pauseApp(){
}
protected void startApp()
throws MIDletStateChangeException {
if( display == null ){
display = Display.getDisplay( this );
initMIDlet();
}
}
}
|
You can improve the counter in various ways, of course, such as by
having it display elapsed time instead of a series of integers. Have fun
exploring the possibilities.
|
|