Friday, December 10, 2010

Touch Menu

Cros-posted at Technè - Blog de Tecnologia do C.E.S.A.R.

Continuing with the details on how to develop touch enabled Java ME applications, today we will see a way of using such screens as input: if the user touchs an area it will trigger an action, like a menu.

This menu will also adapt to screen orientation.

First lets create a class to represent a menu entry. It knows how to paint the entry and stores a Command to be delivered when the user touchs the area:

public class TouchItem {

Command command;

public TouchItem(Command command) {
this.command = command;
}

// for this to work it is necessary that
// Graphics origin to be translated, so it
// is on the top left corner of the item,
// and that the clipping area has the
// width and height of the item
public void paint (Graphics g) {
g.drawString(command.getLabel(),
g.getClipWidth() / 2, g.getClipHeight() / 2,
Graphics.HCENTER | Graphics.BASELINE);
g.drawRect(0, 0, g.getClipWidth(), g.getClipHeight());
}
}

Then we create a class responsible for painting all menu entries. These first two classes can also be used to implement board games like Chess, for example.
public class TouchGrid {

private final TouchItem items[][];
private int itemWidth, itemHeight;
private int x, y;
private int nextLine, nextColumn;

public TouchGrid(int x, int y, int width, int height, int columns, int lines) {
this.items = new TouchItem[columns][lines];
setRectangle(x, y, width, height);
}

public void setRectangle (int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.itemWidth = width / this.items.length;
this.itemHeight = height / this.items[0].length;
}

// fill all columns in a line before moving to
// the next line. Calling this method four times
// will end up with the following menu:
// [ 1, 2 ]
// [ 3, 4 ]
// on a grid with two columns and two lines
public void add (TouchItem item) {
this.items[nextColumn][nextLine] = item;
nextColumn = (nextColumn + 1) % items.length;
if (nextColumn == 0) {
nextLine = (nextLine + 1) % items[0].length;
}
}

public void paint (Graphics g) {
Clip clip = new Clip(g);
int itemX = x;
for (int i = 0; i < items.length; i++) {
int itemY = y;
for (int j = 0; j < items[i].length; j++) {
if (items[i][j] != null) {
// needed by TouchItem to
// paint itself properly
g.setClip(itemX, itemY, itemWidth, itemHeight);
g.translate(itemX, itemY);
items[i][j].paint(g);
// reset Graphics
g.translate(-g.getTranslateX(), -g.getTranslateY());
clip.applyTo(g);
}
itemY += itemHeight;
}
itemX += itemWidth;
}
}

// here we map the point pressed to an item
public TouchItem getTouchItemAt (int x, int y) {
if (x < this.x || y < this.y) {
return null;
}

int i = (x - this.x) / this.itemWidth;
int j = (y - this.y) / this.itemHeight;

if (i >= items.length || j >= items[0].length) {
return null;
}

return items[i][j];
}
}

At paint method above we used below class to store the clip information and restore it:
public class Clip {

private int x, y;
private int width, height;

public Clip (Graphics g) {
x = g.getClipX();
y = g.getClipY();
width = g.getClipWidth();
height = g.getClipHeight();
}

public void applyTo (Graphics g) {
g.setClip(x, y, width, height);
}
}

We need a class that extends Canvas to paint the grid. It will also initialize the grid with the menu options:
public class TouchMenuCanvas extends Canvas {

private TouchGrid touchGrid;
private CommandListener commandListener;

public TouchMenuCanvas () {
final int columns = 2;
final int lines = 3;
int margin = getWidth() / 25;
// 2 = left and right margins
int width = getWidth() - (2 * margin);
// 2 = top and bottom margins
int height = getHeight() - (2 * margin);
touchGrid = new TouchGrid(margin, margin, width, height, columns, lines);
for (int i = 0; i < lines * columns; i++) {
Command c = new Command("item " + (i+1), Command.SCREEN, 1);
touchGrid.add(new TouchItem(c));
}
}

// when orientation changes we need to update
// grid rectangle
protected void sizeChanged(int width, int height) {
int margin = width / 25;
width = width - (2 * margin);
height = height - (2 * margin);
touchGrid.setRectangle(margin, margin, width, height);
}

// we need to override this method because there
// is no getCommandListener on API
public void setCommandListener(CommandListener listener) {
super.setCommandListener(listener);
commandListener = listener;
}

protected void paint(Graphics g) {
g.setColor(0xffffff); // white
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(0); // black
this.touchGrid.paint(g);
}

protected void pointerPressed(int x, int y) {
TouchItem item = this.touchGrid.getTouchItemAt(x, y);
if (item != null) {
commandListener.commandAction(item.command, this);
}
}
}

The last piece is a MIDlet to show the Canvas:
public class TouchMenuMIDlet extends MIDlet implements CommandListener {

TouchMenuCanvas menuCanvas;

public TouchMenuMIDlet() {
menuCanvas = new TouchMenuCanvas();
menuCanvas.setCommandListener(this);
menuCanvas.addCommand(new Command("Exit", Command.EXIT, 1));
}

public void commandAction(Command c, Displayable d) {
if (c.getCommandType() == Command.EXIT) {
this.notifyDestroyed();
} else {
System.out.println(c.getLabel());
}
}

protected void destroyApp(boolean unconditional) {
}

protected void pauseApp() {
}

protected void startApp() throws MIDletStateChangeException {
Display.getDisplay(this).setCurrent(menuCanvas);
}

}

Below is the application running on DefaultCldcPhone1 of Java Platform Micro Edition SDK 3.0 on both orientations.








We hope this helps. See you next time.

No comments: