Touch input best practices

Handle single touch or multi-point touch events

Series 40 full touch and Nokia Asha software platform devices add support for multi-point touch. On these phones, you should set a boolean to disable pointerPressed() and instead use pointersPressed(). Disabling the alternate logic path will help prevent odd multi-platform side effects from responding to both the single touch and multitouch logic.

Handle gestures and FrameAnimation in a delegate

All Nokia Series 40 touch phones and Nokia Asha software platform devices support the Gesture API. There are, however, differences in gesture support among newer phones with multiple touch devices supporting additional pinch gestures. If you are also supporting non-touch devices, they do not support gestures. To work past these differences, make gesture support a selective enhancement isolated to a delegate class by itself and giving the relevant information back to the Canvas for device-neutral handling.

In the code below, notice that the GESTURE_ALL constant has different values on different phones. This example is intended for compilation with Nokia SDK 2.0 or later.

public final class GestureHandler implements FrameAnimatorListener, GestureListener {
    public static final int FRAME_ANIMATOR_VERTICAL = FrameAnimator.FRAME_ANIMATOR_VERTICAL;
    public static final int FRAME_ANIMATOR_HORIZONTAL = FrameAnimator.FRAME_ANIMATOR_HORIZONTAL;
    public static final int FRAME_ANIMATOR_FREE_ANGLE = FrameAnimator.FRAME_ANIMATOR_FREE_ANGLE;
    public static final int FRAME_ANIMATOR_FRICTION_LOW = FrameAnimator.FRAME_ANIMATOR_FRICTION_LOW;
    public static final int FRAME_ANIMATOR_FRICTION_MEDIUM = FrameAnimator.FRAME_ANIMATOR_FRICTION_MEDIUM;
    public static final int FRAME_ANIMATOR_FRICTION_HIGH = FrameAnimator.FRAME_ANIMATOR_FRICTION_HIGH;
    private GestureCanvas canvas;
    private final FrameAnimator animator = new FrameAnimator();
    private GestureInteractiveZone giz;

    public GestureHandler() {
        try {
            giz = new GestureInteractiveZone(GestureInteractiveZone.GESTURE_ALL);
        } catch (Throwable t) {
            //#debug
            Log.e(L.class.getName(), "Cannot register GESTURE_ALL, backwards compatibility fallback", e);
            giz = new GestureInteractiveZone(63); // GESTURE_ALL had a different value in SDK 1.1 and 1.0
        }
    }
    
    public void setCanvas(final GestureCanvas canvas) {
        this.canvas = canvas;
    }

    public void gestureAction(final Object container, final GestureInteractiveZone gestureInteractiveZone, final GestureEvent ge) {
        switch (ge.getType()) {
            // Pinch to force reload
            case GestureInteractiveZone.GESTURE_PINCH:
                canvas.gesturePinch(
                        ge.getPinchDistanceStarting(),
                        ge.getPinchDistanceCurrent(),
                        ge.getPinchDistanceChange(),
                        ge.getPinchCenterX(),
                        ge.getPinchCenterY(),
                        ge.getPinchCenterChangeX(),
                        ge.getPinchCenterChangeY());
                break;

            case GestureInteractiveZone.GESTURE_TAP:
                canvas.gestureTap(ge.getStartX(), ge.getStartY());
                break;

            case GestureInteractiveZone.GESTURE_LONG_PRESS:
                canvas.gestureLongPress(ge.getStartX(), ge.getStartY());
                break;

            case GestureInteractiveZone.GESTURE_LONG_PRESS_REPEATED:
                canvas.gestureLongPressRepeated(ge.getStartX(), ge.getStartY());
                break;

            case GestureInteractiveZone.GESTURE_DRAG:
                canvas.gestureDrag(ge.getStartX(), canvas.getScrollY(), ge.getDragDistanceX(), ge.getDragDistanceY());
                break;

            case GestureInteractiveZone.GESTURE_DROP:
                canvas.gestureDrop(ge.getStartX(), ge.getStartY(), ge.getDragDistanceX(), ge.getDragDistanceY());
                break;

            case GestureInteractiveZone.GESTURE_FLICK:
                canvas.gestureFlick(ge.getStartX(), canvas.getScrollY(), ge.getFlickDirection(), ge.getFlickSpeed(), ge.getFlickSpeedX(), ge.getFlickSpeedY());
                break;

            default:
                ;
        }
    }

    public void animate(final FrameAnimator animator, final int x, final int y, final short delta, final short deltaX, final short deltaY, final boolean lastFrame) {
        canvas.animate(x, y, delta, deltaX, deltaY, lastFrame);
    }

    public void animateDrag(final int x, final int y) {
        animator.drag(x, y);
    }

    public void kineticScroll(final int startSpeed, final int direction, final int friction, final float angle) {
        animator.kineticScroll(startSpeed, direction, friction, angle);
    }

    public void stopAnimation() {
        try {
            animator.stop();
        } catch (Exception e) {
            Log.e("stopAnimation", "Cannot stop", e);
        }
    }

    /**
     * Unregister gesture and animation events (there is a max # allowed at any
     * one time)
     *
     */
    public void unregister() {
        GestureRegistrationManager.unregister(canvas, giz);
        animator.unregister();
    }
    
    public void updateCanvasSize() {
        giz.setRectangle(0, 0, canvas.getWidth(), canvas.getHeight());
    }

    /**
     * Register for gesture and animation events when the parent becomes visible
     *
     */
    public void register() {
        GestureRegistrationManager.register(canvas, giz);
        updateCanvasSize();
        GestureRegistrationManager.setListener(canvas, this);
        animator.register(0, 0, (short) 0, (short) 0, this);
    }
}

GestureInteractiveZone

When creating a GestureInteractiveZone object that uses several gestures, the best way of declaring the gestures on SDK 2.0 is to use GESTURE_ALL. However, this will lead to problems on older SDK 1.1 devices that support the GestureInteractiveZone class, but not all the gestures. The following gestures are known to fail on older SDKs:

  • GESTURE_ALL

  • GESTURE_PINCH

  • GESTURE_RECOGNITION_END

  • GESTURE_RECOGNITION_START

Fortunately, we can catch the exception thrown here, so the solution is to declare the GestureInteractiveZone inside a try block, and catch the exception. If there is no need for the unsupported gestures, it is best not to use them.

The following example declares a GestureInteractiveZone with all the supported gestures on SDK 2.0, but falls back to just a few supported ones on older devices.

// Register for gesturevents
final GestureInteractiveZone giz;
GestureInteractiveZone tmp = null;
try {
  tmp = new GestureInteractiveZone(
                GestureInteractiveZone.GESTURE_ALL);
} catch (Throwable t){
  Log.l.log("Some gesture events not supported.", "", e);
  tmp = new GestureInteractiveZone(
  	GestureInteractiveZone.GESTURE_DRAG | 
  	GestureInteractiveZone.GESTURE_FLICK | 
  	GestureInteractiveZone.GESTURE_TAP);
} finally {
  giz = tmp;
}

An example app using this technique:

The Zoom demo under Series 40 UI Component Demos handles gestures in a backward compatible way. See package com.nokia.example.utils.gesture for implementation details.

Hide the virtual keyboard menu item

When using the Canvas class on touch and type and full touch devices, the device assumes there will be keyboard input, unless otherwise specified. To allow the user to enable the virtual keyboard, the phone will automatically add a menu item to open the virtual keyboard. This is visible on the Canvas as an opaque button, if the MIDlet is viewed in full-screen mode.

If you bring up a TextEditor when needed, or if there are no actions tied to the keyboard inputs, there is no need for this menu item. You may hide it as follows:

String keyboardType = System.getProperty("com.nokia.keyboard.type");
      if (keyboardType.equals("OnekeyBack") || keyboardType.equals("None")) {
          // full touch or Nokia Asha software platform device detected 
        //Something here
          com.nokia.mid.ui.VirtualKeyboard.hideOpenKeypadCommand(true);
      }

An example app using this technique:

WeatherApp example uses an alternate approach with System.getProperty method to check com.nokia.keyboard.type property to hide the virtual keyboard option menu item on full-touch devices.