This example MIDlet shows you how to create a simple touch-based paint application. The MIDlet allows the user to draw on the screen, change the brush size and color, clear the screen, and save the drawn image in the BMP file format to the default photo folder.
Paint MIDlet scales quite nicely to devices running the new Java Runtime 2.0 for Series 40.
To optimize the Paint MIDlet for the full touch UI:
Add multipoint touch support.
Since the Paint MIDlet was designed for single-touch use, some problematic behavior occurs in multipoint touch devices. For example, with two moving touch points Paint draws a zig-zag pattern. So it is better to handle touch points separately.
Unfortunately, multipoint touch does not function as well on resistive screens as on with capacitive screen.
Normally touch events can be handled by implementing the Canvas.pointerPressed
, Canvas.pointerDragged
and Canvas.pointerReleased
methods. However, multipoint
touch event handling involves two new classes: MultipointTouch
and MultipointTouchListener
. These classes can
be used for receiving notifications whenever pointerChanged
events occur, which identify changed touch points by their IDs.
The current position and state of a touch point can be queried using
the provided IDs. A state can be one of the following: POINTER_PRESSED
, POINTER_DRAGGED
and POINTER_RELEASED
. They could be directly mapped to the existing pointer methods,
already implemented by Paint.
Our goal is to avoid the zig-zag pattern by drawing separate lines between individual touch points. To achieve this, we also need to provide the IDs to the drawing algorithm. To draw multiple lines concurrently, we need to have separate buffers for each of the lines. This approach will also scale for more than two touch points, and will work even if the multipoint touch is not supported. The solution is to modify the existing pointer events to take an ID when saving the input events to drawing buffer.
The following code snippet shows how the events are saved to the buffer. Note that the buffers and previous coordinates are stored as arrays, which are indexed using the touch point IDs.
private int[] previousX; private int[] previousY; private Vector[] buffers; public void pointerDragged(int x, int y, int id) { buffers[id].addElement(new DrawableLine(previousX[id], previousY[id], x, y)); previousX[id] = x; previousY[id] = y; } // Utilize also the canvas pointer method in case there's no multitouch available public void pointerDragged(int x, int y) { if (TOUCH_POINTS == 1) { // Prevent redundant calls pointerDragged(x, y, 0); } }
Map the pointerChanged
events to our
pointer event handlers.
/** * Deliver pointer events to listener * @param ids */ public void pointersChanged(int[] ids) { for (int i = 0; i < ids.length; i++) { int id = ids[i]; int x = MultipointTouch.getX(id); int y = MultipointTouch.getY(id); switch (MultipointTouch.getState(id)) { case MultipointTouch.POINTER_PRESSED: pointerPressed(x, y, id); break; case MultipointTouch.POINTER_DRAGGED: pointerDragged(x, y, id); break; case MultipointTouch.POINTER_RELEASED: pointerReleased(x, y, id); break; } } }
The first code snippet assumes that the multipoint
touch is not always supported. To be able to preserve backwards compatibility,
the two new classes, MultipointTouch
and MultipointTouchListener
, cannot be used directly. Instead
load the classes dynamically if they exist in the runtime. See How to use an optional API in Java ME for
the philosophy behind this approach.
Save dialog uses full-screen text box instead of the custom save dialog.
On full-touch platform, the save dialog appears behind the virtual
keypad. So, use a native full-screen text box. Note, that on Java
Runtime 2.0 for Series 40 a TextBox
screen contains,
by default, a back button, which appears under the virtual keyboard.
Don’t forget to map a command to it.
try { Class.forName("com.nokia.mid.ui.VirtualKeyboard"); Command backCmd = new Command("Back", Command.BACK, 1); textBox.addCommand(backCmd); } catch (ClassNotFoundException cnfe) { // No virtual keyboard }