Sunday, April 17, 2011

sizeChanged not called

To achieve the Single Jar theory we must deal with handsets fragmentation upfront.
One special piece of these fragments is the orientation change.

Screen orientations are: portrait (height > width) and landscape (width > height).

When the change happens the Java Virtual Machine should notify the running MIDlet with a call to sizeChanged method on the currently displayed Canvas.
Unfortunately this is not guaranteed to happen.

To make sure your application is always presented the right way you can store the last sizeChanged parameter values on attributes and check them on paint method.
Check the code below:


class C extends Canvas {
int lastWidth, lastHeight;
protected void sizeChanged(int w, int h) {
lastWidth = w;
lastHeight = h;
// adjust your user interface to the
// new width and height
// ...
}
protected void paint(Graphics g) {
if (super.getWidth() != lastWidth
|| super.getHeight() != lastHeight) {
sizeChanged(super.getWidth(), super.getHeight());
}
// paint your user interface
// ...
}
}

Related topics:

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.

Thursday, December 9, 2010

Touch Screen support

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

More and more cell phones have touch screens. And when we hear about them the first thing that we think are the most expensive smartphones. Big screens and amazing computer power.

But there are also feature phones with such screens. And amongst them there is a lot supporting Java ME. So, how do you enable an application to get the user's "point"?

Since MIDP 1.0 Canvas already had the following callback methods: pointerPressed, pointerDragged and pointerReleased. And for information there were also hasPointerEvents and hasPointerMotionEvents.

TIP: SDK 3.0 emulator already have touch support, but if you are using WTK 2.5 go to wtklib\devices\DefaultColorPhone\DefaultColorPhone.properties and change the value of touch_screen to true.

To better understand these pointer methods here is a simple example. A Canvas where we can draw freely. It bevahes like Microsoft Paint pencil.

public class TouchCanvas extends Canvas {
// starting point of the line drawn at paint method
int x1 = -1, y1 = -1;
// line final point
int x2, y2;
// flag used to know when to clear the screen
boolean clear = true;

protected void pointerPressed(int x, int y) {
x1 = x;
y1 = y;
}
protected void pointerReleased(int x, int y) {
x1 = y1 = -1;
}
protected void pointerDragged(int x, int y) {
x2 = x;
y2 = y;
repaint();
}

protected void paint(Graphics g) {
if (clear) {
g.setColor(0xffffff); // white
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(0); // black
clear = false;
}

if (x1 >= 0 && y1 >= 0) {
g.drawLine(x1, y1, x2, y2);
x1 = x2;
y1 = y2;
}
}
}

The MIDlet below shows the Canvas and give the user a option to clear the screen.

public class TouchMIDlet extends MIDlet implements CommandListener {

TouchCanvas touchCanvas = new TouchCanvas();

public TouchMIDlet() {
touchCanvas.addCommand(new Command("Clear", Command.OK, 1));
touchCanvas.addCommand(new Command("Exit", Command.EXIT, 1));
touchCanvas.setCommandListener(this);
}

protected void destroyApp(boolean unconditional) { }

protected void pauseApp() { }

protected void startApp() {
Display.getDisplay(this).setCurrent(touchCanvas);
}

public void commandAction(Command c, Displayable d) {
if (c.getCommandType() == Command.EXIT) {
notifyDestroyed();
} else {
touchCanvas.clear = true;
touchCanvas.repaint();
}
}
}

For a more complex sample lets change two classes from Bar Chart to add touch dragging. First we add the following method to BarChart class:
public int getBarWidth (Font font) {
return font.stringWidth(widestName);
}

Now we add the following code to BarChartCanvas:
private int lastX, lastY;
protected void pointerPressed(int x, int y) {
lastX = x;
lastY = y;
}
protected void pointerDragged(int x, int y) {
int w = barChart.getBarWidth(Font.getDefaultFont());
// dragged a distance bigger than the bars width
if (lastX - x >= w || lastY - y >= w) {
barChart.nextBar();
repaint();
lastX = x;
} if (x - lastX >= w || y - lastY >= w) {
barChart.previousBar();
repaint();
lastX = x;
}
}

The smaller the bars be smoother the movement will be. This code also works on landscape mode (SDK 3.0 Emulator: View .. Orientation .. 270).

Another common action is the long press. As we do not have a callback method for it we need to count the time the user is pressing and not dragging or releasing. Below is a way to change our TouchCanvas:

private Timer timer;
protected void showNotify() {
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
// long press x and y
int lpx = -1, lpy = -1;
public void run() {
if (x1 >= 0) {
if (lpx == x1 && lpy == y1) {
lpx = lpy = -1;
System.out.println("long press");
} else {
lpx = x1;
lpy = y1;
}
}
}
},
// starting now and checking at every second
0, 1000);
}
protected void hideNotify() {
timer.cancel();
timer = null;
}

We hope this helps. See you next time.

Related topics: