GestureList.java
/*
* Copyright © 2012 Nokia Corporation. All rights reserved.
* Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation.
* Oracle and Java are trademarks or registered trademarks of Oracle and/or its
* affiliates. Other product and company names mentioned herein may be trademarks
* or trade names of their respective owners.
* See LICENSE.TXT for license information.
*/
package com.nokia.example.attractions.views.list;
import com.nokia.example.attractions.views.ViewMaster;
import com.nokia.mid.ui.frameanimator.FrameAnimator;
import com.nokia.mid.ui.frameanimator.FrameAnimatorListener;
import com.nokia.mid.ui.gestures.GestureEvent;
import com.nokia.mid.ui.gestures.GestureInteractiveZone;
import com.nokia.mid.ui.gestures.GestureListener;
import com.nokia.mid.ui.gestures.GestureRegistrationManager;
/**
* List view custom UI control with gesture controls.
*/
final class GestureList
extends List
implements GestureListener, FrameAnimatorListener {
private final static int GESTURES = GestureInteractiveZone.GESTURE_DRAG
| GestureInteractiveZone.GESTURE_TAP
| GestureInteractiveZone.GESTURE_FLICK;
private final FrameAnimator animator;
private boolean scrollingActive;
private int pendingGestureEvent;
private Thread delayThread;
private boolean enabled = false;
private final GestureInteractiveZone zone;
private final ViewMaster view;
GestureList() {
zone = new GestureInteractiveZone(GESTURES);
animator = new FrameAnimator();
view = ViewMaster.getInstance();
}
public final void disable() {
if (!enabled) {
return;
}
enabled = false;
GestureRegistrationManager.unregister(view, zone);
super.disable();
animator.unregister();
}
public final void enable() {
if (enabled) {
return;
}
enabled = true;
super.enable();
// Use default values for maxFps an maxPps
// (zero param means that default is used).
final short maxFps = 0;
final short maxPps = 0;
if (animator.register((short) 0, (short) 0, maxFps, maxPps, this)
!= true) {
throw new RuntimeException("FrameAnimator.register() failed!");
}
if (GestureRegistrationManager.register(view, zone) != true) {
throw new RuntimeException(
"GestureRegistrationManager.register() failed!");
}
GestureRegistrationManager.setListener(view, this);
}
public final void resize(int x, int y, int width, int height) {
super.resize(x, y, width, height);
zone.setRectangle(x, y, width, height);
}
/**
* @see com.nokia.mid.ui.frameanimator.FrameAnimatorListener#animate(
* com.nokia.mid.ui.frameanimator.FrameAnimator, int, int, short, short,
* short, boolean)
*/
public final void animate(FrameAnimator animator, int x, int y, short delta,
short deltaX, short deltaY, boolean lastFrame) {
addTranslateY(deltaY);
// Scrolling is no longer active if this is the last frame
scrollingActive = !lastFrame;
// Request repaint from ViewMaster
view.forceDraw();
}
/**
* Stops scrolling.
*/
private void stopScrolling() {
scrollingActive = false;
animator.stop();
}
/**
* @see com.nokia.mid.ui.gestures.GestureListener#gestureAction(
* java.lang.Object, com.nokia.mid.ui.gestures.GestureInteractiveZone,
* com.nokia.mid.ui.gestures.GestureEvent)
*/
public final void gestureAction(Object container,
GestureInteractiveZone zone, GestureEvent event) {
// Block gesture handling if already handling one,
// this may happen due to delayed handling of events
// (delay is needed for showing focus on selected item)
if (pendingGestureEvent > 0) {
return;
}
resetFocusedRowIndex();
switch (event.getType()) {
case GestureInteractiveZone.GESTURE_DRAG: {
if (scrollingActive) {
stopScrolling();
}
else {
addTranslateY(event.getDragDistanceY());
view.forceDraw();
}
break;
}
case GestureInteractiveZone.GESTURE_TAP: {
if (scrollingActive) {
stopScrolling();
}
else {
handleSelectEvent(event);
}
break;
}
case GestureInteractiveZone.GESTURE_FLICK: {
// Start vertical flick only if the gesture is
// more vertical than horizontal.
float angle = Math.abs(event.getFlickDirection());
if ((angle > (Math.PI / 4) && angle < (3 * Math.PI / 4))) {
int startSpeed = event.getFlickSpeedY();
int direction = FrameAnimator.FRAME_ANIMATOR_VERTICAL;
int friction = FrameAnimator.FRAME_ANIMATOR_FRICTION_LOW;
scrollingActive = true;
animator.kineticScroll(startSpeed, direction, friction, 0);
}
break;
}
default:
break;
}
}
/**
* Common handler function for selection events.
*
* @param event Gesture event.
*/
private void handleSelectEvent(GestureEvent event) {
setFocusedRowIndex(getRowAt(event.getStartX(), event.getStartY()));
if (isFocusedRowIndex()) {
view.draw();
// Delay the event handling,
// so that user gets a chance to see the focus.
// Gesture is stored so that we can handle it later on.
pendingGestureEvent = event.getType();
delayThread = new Thread() {
public void run() {
try {
Thread.sleep(100);
}
catch (InterruptedException e) {
return;
}
if (pendingGestureEvent > 0) {
switch (pendingGestureEvent) {
case GestureInteractiveZone.GESTURE_TAP:
select();
break;
default:
break;
}
resetFocusedRowIndex();
pendingGestureEvent = 0;
delayThread = null;
}
}
};
delayThread.start();
}
}
/**
* Finds the row with the given coordinates.
*
* @param x X-coordinate.
* @param y Y-coordinate.
* @return Row at the given coordinates or -1 if no row found.
*/
private int getRowAt(int x, int y) {
int listY = y - getTranslateY();
int heightSoFar = 0;
int heightNext = 0;
// Go through all items (pretend that list item heights can vary).
if (data != null) {
for (int i = 0; i < data.size(); i++) {
heightSoFar = heightNext;
heightNext += itemHeight();
if (listY >= heightSoFar && listY <= heightNext) {
return i;
}
}
}
return -1;
}
protected final void stopScrollAnimation() {
if (scrollingActive) {
stopScrolling();
}
}
}