Namespaces

Variants
Actions

Please note that as of October 24, 2014, the Nokia Developer Wiki will no longer be accepting user contributions, including new entries, edits and comments, as we begin transitioning to our new home, in the Windows Phone Development Wiki. We plan to move over the majority of the existing entries over the next few weeks. Thanks for all your past and future contributions.

Gesture Recognizer for Java ME

From Wiki
Jump to: navigation, search
Article Metadata
Code ExampleTested with
Devices(s): Asha 303, X3-02
Compatibility
Platform(s): Series 40 SDK 1.0
Series 40
Device(s): touch devices
Article
Created: grahamhughes (21 Mar 2010)
Updated: bandarap (20 Aug 2012)
Last edited: hamishwillee (13 Aug 2013)
Featured Article
25 Apr
2010

This article provides source and usage examples for the Java ME GestureRecognizer class; a class that can recognise gestures: tap, tap'n'hold, drag and circle.

Overview

The GestureRecognizer class recognizes the following kinds of gesture:

  • tap - a brief touch in a single position
  • tap'n'hold - a prolonged touch in a single position
  • drag - a swift movement from one side of the screen towards the opposite side
  • circle - a swift circular motion, from the top of the circle, in either clockwise or anti-clockwise directions

Performance can be tweaked by adjusting the values of DRAG_THRESHOLD and HOLD_THRESHOLD. HOLD_THRESHOLD is simply the amount of time a tap must persist before it becomes a tap'n'hold. DRAG_THRESHOLD is used to desensitize the algorithm to small movements during a gesture. Currently, this can cause failure to recognize circular gestures if they are drawn too slowly, or if the circle is very small.

Source code

/**
* Recognize a tap, drag or circular gesture, from a series of (x,y) points.
* @author Graham Hughes
*/

public class GestureRecognizer {
// constants returned from getGesture()
private static final int NONE = 0;
public static final int TAP = 1;
public static final int TAP_N_HOLD = 2;
public static final int DRAG_UP = 3;
public static final int DRAG_DOWN = 4;
public static final int DRAG_LEFT = 5;
public static final int DRAG_RIGHT = 6;
public static final int CIRCLE_CLOCK = 7;
public static final int CIRCLE_ANTI = 8;
public static final int UNKNOWN = 9;
 
// any movement smaller than this is ignored (treated as "tap" not "drag")
private static final int DRAG_THRESHOLD = 4;
 
// minimum milliseconds for a "hold"
private static final int HOLD_THRESHOLD = 500;
 
// PDX = positive delta X, NDY = negative delta Y, etc.
private static final int SEEN_PDX_PDY = 1 << 0;
private static final int SEEN_NDX_PDY = 1 << 1;
private static final int SEEN_NDX_NDY = 1 << 2;
private static final int SEEN_PDX_NDY = 1 << 3;
private static final int SEEN_PDX = 1 << 4;
private static final int SEEN_NDX = 1 << 5;
private static final int SEEN_PDY = 1 << 6;
private static final int SEEN_NDY = 1 << 7;
 
private static final int SEEN_ALL = SEEN_PDX | SEEN_NDX | SEEN_PDY | SEEN_NDY;
 
private long startTime;
private int startX;
private int startY;
private int lastX;
private int lastY;
private int minX;
private int minY;
private int maxX;
private int maxY;
private int seen;
private int[] sequence = new int[4];
private int sequenceIndex;
private int gesture;
 
/** Create recognizer, with starting (x,y) */
public GestureRecognizer(int x, int y) {
startTime = System.currentTimeMillis();
startX = lastX = minX = maxX = x;
startY = lastY = minY = maxY = y;
}
 
/** Add (x,y) position to gesture sequence */
public void addMotion(int newX, int newY) {
checkIncomplete();
 
int dx = newX - lastX;
int dy = newY - lastY;
 
int seenThisTime = 0;
 
if (dx < -DRAG_THRESHOLD) {
seenThisTime |= SEEN_NDX;
lastX = newX;
if (newX < minX) {
minX = newX;
}
} else if (dx > DRAG_THRESHOLD) {
seenThisTime |= SEEN_PDX;
lastX = newX;
if (newX > maxX) {
maxX = newX;
}
}
 
if (dy < -DRAG_THRESHOLD) {
seenThisTime |= SEEN_NDY;
lastY = newY;
if (newY < minY) {
minY = newY;
}
} else if (dy > DRAG_THRESHOLD) {
seenThisTime |= SEEN_PDY;
lastY = newY;
if (newY > maxY) {
maxY = newY;
}
}
 
switch (seenThisTime) {
case SEEN_PDX | SEEN_PDY:
seenThisTime |= SEEN_PDX_PDY;
addToSequence(SEEN_PDX_PDY);
break;
case SEEN_PDX | SEEN_NDY:
seenThisTime |= SEEN_PDX_NDY;
if (sequenceIndex > 0) {
addToSequence(SEEN_PDX_NDY);
}
break;
case SEEN_NDX | SEEN_PDY:
seenThisTime |= SEEN_NDX_PDY;
addToSequence(SEEN_NDX_PDY);
break;
case SEEN_NDX | SEEN_NDY:
seenThisTime |= SEEN_NDX_NDY;
if (sequenceIndex > 0) {
addToSequence(SEEN_NDX_NDY);
}
break;
}
 
if (seenThisTime != 0) {
seen |= seenThisTime;
}
}
 
/** End the gesture, with terminating (x,y) */
public void endGesture(int endX, int endY) {
addMotion(endX, endY);
 
if (seen == 0) {
if (System.currentTimeMillis() > startTime + HOLD_THRESHOLD) {
gesture = TAP_N_HOLD;
} else {
gesture = TAP;
}
} else if ((seen & SEEN_ALL) != SEEN_ALL) {
int dx = maxX - minX;
int dy = maxY - minY;
 
if ((seen & SEEN_NDY) != 0 && (seen & SEEN_PDY) == 0 && (dy > dx)) {
gesture = DRAG_UP;
}
 
if ((seen & SEEN_PDY) != 0 && (seen & SEEN_NDY) == 0 && (dy > dx)) {
gesture = DRAG_DOWN;
}
 
if ((seen & SEEN_NDX) != 0 && (seen & SEEN_PDX) == 0 && (dx > dy)) {
gesture = DRAG_LEFT;
}
 
if ((seen & SEEN_PDX) != 0 && (seen & SEEN_NDX) == 0 && (dx > dy)) {
gesture = DRAG_RIGHT;
}
} else {
if (sequenceEquals(new int[] {SEEN_PDX_PDY, SEEN_NDX_PDY, SEEN_NDX_NDY, SEEN_PDX_NDY})) {
gesture = CIRCLE_CLOCK;
} else if (sequenceEquals(new int[] {SEEN_NDX_PDY, SEEN_PDX_PDY, SEEN_PDX_NDY, SEEN_NDX_NDY})) {
gesture = CIRCLE_ANTI;
}
}
 
if (gesture == NONE) {
gesture = UNKNOWN;
}
}
 
/** @return the gesture type */
public int getGesture() {
checkComplete();
return gesture;
}
 
/** @return the "x" position for "tap" gestures */
public int getTapX() {
checkTap();
return startX;
}
 
/** @return the "y" position for "tap" gestures */
public int getTapY() {
checkTap();
return startY;
}
 
/** @return the left-x position for the area of a circular gesture */
public int getAreaX() {
checkArea();
return minX;
}
 
/** @return the top-y position for the area of a circular gesture */
public int getAreaY() {
checkArea();
return minY;
}
 
/** @return the width of the area of a circular gesture */
public int getAreaWidth() {
checkArea();
return maxX - minX;
}
 
/** @return the height of the area of a circular gesture */
public int getAreaHeight() {
checkArea();
return maxY - minY;
}
 
private void checkIncomplete() {
if (gesture != NONE) {
throw new IllegalStateException("gesture complete");
}
}
 
private void checkComplete() {
if (gesture == NONE) {
throw new IllegalStateException("gesture not complete");
}
}
 
private void checkTap() {
checkComplete();
if (gesture != TAP && gesture != TAP_N_HOLD) {
throw new IllegalStateException("gesture not a tap");
}
}
 
private void checkArea() {
checkComplete();
if (gesture != CIRCLE_CLOCK && gesture != CIRCLE_ANTI) {
throw new IllegalStateException("gesture not an area");
}
}
 
private boolean sequenceContains(int n) {
boolean contains = false;
for (int i = 0; i < sequenceIndex; i++) {
if (sequence[i] == n) {
contains = true;
}
}
return contains;
}
 
private void addToSequence(int n) {
if (!sequenceContains(n) && sequenceIndex < sequence.length) {
System.out.println(sequenceIndex + ": " + n);
sequence[sequenceIndex] = n;
sequenceIndex++;
}
}
 
private boolean sequenceEquals(int[] a) {
boolean equals;
if (sequenceIndex == a.length) {
equals = true;
for (int i = 0; i < sequenceIndex; i++) {
if (sequence[i] != a[i]) {
equals = false;
}
}
} else {
equals = false;
}
return equals;
}
}

Usage Example

import javax.microedition.lcdui.*;
import javax.microedition.midlet.MIDlet;
 
public class Gesture extends MIDlet implements CommandListener {
 
private Canvas canvas;
 
public void startApp() {
if (canvas == null) {
canvas = new TestCanvas();
canvas.addCommand(new Command("Exit", Command.EXIT, 0));
canvas.setCommandListener(this);
}
Display.getDisplay(this).setCurrent(canvas);
}
 
public void pauseApp() {
// empty
}
 
public void destroyApp(boolean must) {
// empty
}
 
public void commandAction(Command c, Displayable d) {
if (c.getCommandType() == Command.EXIT) {
notifyDestroyed();
}
}
 
private static class TestCanvas extends Canvas {
private GestureRecognizer recognizer;
private int gesture;
private boolean show;
 
protected void paint(Graphics g) {
g.setColor(0x400000);
g.fillRect(0, 0, getWidth(), getHeight());
 
if (show) {
g.setColor(0xffff00);
String s;
boolean drawTap = false;
boolean drawArea = false;
 
switch (gesture) {
case GestureRecognizer.TAP:
s = "tap";
drawTap = true;
break;
case GestureRecognizer.TAP_N_HOLD:
s = "tap'n'hold";
drawTap = true;
break;
case GestureRecognizer.DRAG_UP:
s = "drag up";
break;
case GestureRecognizer.DRAG_DOWN:
s = "drag down";
break;
case GestureRecognizer.DRAG_LEFT:
s = "drag left";
break;
case GestureRecognizer.DRAG_RIGHT:
s = "drag right";
break;
case GestureRecognizer.CIRCLE_CLOCK:
s = "circle clockwise";
drawArea = true;
break;
case GestureRecognizer.CIRCLE_ANTI:
s = "circle anti-clockwise";
drawArea = true;
break;
default:
s = "unknown";
break;
}
 
g.drawString(s, getWidth() / 2, getHeight(), Graphics.HCENTER | Graphics.BOTTOM);
 
if (drawTap) {
g.drawRect(recognizer.getTapX() - 4, recognizer.getTapY() - 4, 8, 8);
}
if (drawArea) {
g.drawRect(recognizer.getAreaX(), recognizer.getAreaY(), recognizer.getAreaWidth(), recognizer.getAreaHeight());
}
}
}
 
protected void pointerPressed(int x, int y) {
recognizer = new GestureRecognizer(x, y);
 
show = false;
repaint();
}
 
protected void pointerDragged(int x, int y) {
recognizer.addMotion(x, y);
}
 
protected void pointerReleased(int x, int y) {
recognizer.endGesture(x, y);
gesture = recognizer.getGesture();
 
show = true;
repaint();
}
}
}
This page was last modified on 13 August 2013, at 05:47.
211 page views in the last 30 days.

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×