/*
* Copyright 2010, 2011, 2012 mapsforge.org
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mapsforge.android.maps.inputhandling;
import org.mapsforge.android.maps.MapView;
import org.mapsforge.android.maps.MapViewPosition;
import org.mapsforge.core.model.Point;
import android.content.Context;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ViewConfiguration;
/**
* Implementation for multi-touch capable devices, requires Android API level 8 or higher.
*/
public class TouchEventHandler {
private static final int INVALID_POINTER_ID = -1;
/**
* @param motionEvent
* the motion event whose action should be returned.
* @return the int value of the action as defined in the {@link MotionEvent} class.
*/
public static int getAction(MotionEvent motionEvent) {
return motionEvent.getAction() & MotionEvent.ACTION_MASK;
}
private int activePointerId;
private final float doubleTapDelta;
private final int doubleTapTimeout;
private long doubleTouchStart;
private final float mapMoveDelta;
private final MapViewPosition mapViewPosition;
private boolean moveThresholdReached;
private boolean previousEventTap;
private Point previousPosition;
private Point previousTapPosition;
private long previousTapTime;
private final ScaleGestureDetector scaleGestureDetector;
/**
* @param context
* a reference to the global application environment.
* @param mapView
* the MapView from which the touch events are coming from.
*/
public TouchEventHandler(Context context, MapView mapView) {
this.mapViewPosition = mapView.getMapViewPosition();
ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
this.mapMoveDelta = viewConfiguration.getScaledTouchSlop();
this.doubleTapDelta = viewConfiguration.getScaledDoubleTapSlop();
this.doubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
this.scaleGestureDetector = new ScaleGestureDetector(context, new ScaleListener(mapView));
}
/**
* Handles a {@code MotionEvent} from the touch screen.
*
* @param motionEvent
* the event to be handled.
* @return true if the event was handled, false otherwise.
*/
public boolean onTouchEvent(MotionEvent motionEvent) {
int action = getAction(motionEvent);
// workaround for a bug in the ScaleGestureDetector, see Android issue #12976
if (action != MotionEvent.ACTION_MOVE || motionEvent.getPointerCount() > 1) {
this.scaleGestureDetector.onTouchEvent(motionEvent);
}
switch (action) {
case MotionEvent.ACTION_DOWN:
return onActionDown(motionEvent);
case MotionEvent.ACTION_MOVE:
return onActionMove(motionEvent);
case MotionEvent.ACTION_UP:
return onActionUp(motionEvent);
case MotionEvent.ACTION_CANCEL:
return onActionCancel();
case MotionEvent.ACTION_POINTER_DOWN:
return onActionPointerDown(motionEvent);
case MotionEvent.ACTION_POINTER_UP:
return onActionPointerUp(motionEvent);
}
// the event was not handled
return false;
}
private boolean onActionCancel() {
this.activePointerId = INVALID_POINTER_ID;
return true;
}
private boolean onActionDown(MotionEvent motionEvent) {
this.activePointerId = motionEvent.getPointerId(0);
this.previousPosition = new Point(motionEvent.getX(), motionEvent.getY());
this.moveThresholdReached = false;
return true;
}
private boolean onActionMove(MotionEvent motionEvent) {
if (this.scaleGestureDetector.isInProgress()) {
return true;
}
int pointerIndex = motionEvent.findPointerIndex(this.activePointerId);
float moveX = (float) (motionEvent.getX(pointerIndex) - this.previousPosition.x);
float moveY = (float) (motionEvent.getY(pointerIndex) - this.previousPosition.y);
if (!this.moveThresholdReached) {
if (Math.abs(moveX) > this.mapMoveDelta || Math.abs(moveY) > this.mapMoveDelta) {
this.moveThresholdReached = true;
this.previousPosition = new Point(motionEvent.getX(pointerIndex), motionEvent.getY(pointerIndex));
}
return true;
}
this.previousPosition = new Point(motionEvent.getX(pointerIndex), motionEvent.getY(pointerIndex));
this.mapViewPosition.moveCenter(moveX, moveY);
return true;
}
private boolean onActionPointerDown(MotionEvent motionEvent) {
this.doubleTouchStart = motionEvent.getEventTime();
return true;
}
private boolean onActionPointerUp(MotionEvent motionEvent) {
// extract the index of the pointer that left the touch sensor
int pointerIndex = (motionEvent.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
if (motionEvent.getPointerId(pointerIndex) == this.activePointerId) {
// the active pointer has gone up, choose a new one
if (pointerIndex == 0) {
pointerIndex = 1;
} else {
pointerIndex = 0;
}
this.activePointerId = motionEvent.getPointerId(pointerIndex);
this.previousPosition = new Point(motionEvent.getX(pointerIndex), motionEvent.getY(pointerIndex));
}
long doubleTouchTime = motionEvent.getEventTime() - this.doubleTouchStart;
if (doubleTouchTime < this.doubleTapTimeout) {
// multi-touch tap event, zoom out
this.previousEventTap = false;
this.mapViewPosition.zoomOut();
}
return true;
}
private boolean onActionUp(MotionEvent motionEvent) {
int pointerIndex = motionEvent.findPointerIndex(this.activePointerId);
this.activePointerId = INVALID_POINTER_ID;
if (this.moveThresholdReached) {
this.previousEventTap = false;
} else {
if (this.previousEventTap) {
double diffX = Math.abs(motionEvent.getX(pointerIndex) - this.previousTapPosition.x);
double diffY = Math.abs(motionEvent.getY(pointerIndex) - this.previousTapPosition.y);
long doubleTapTime = motionEvent.getEventTime() - this.previousTapTime;
if (diffX < this.doubleTapDelta && diffY < this.doubleTapDelta && doubleTapTime < this.doubleTapTimeout) {
// double-tap event, zoom in
this.previousEventTap = false;
this.mapViewPosition.zoomIn();
return true;
}
} else {
this.previousEventTap = true;
}
this.previousTapPosition = new Point(motionEvent.getX(pointerIndex), motionEvent.getY(pointerIndex));
this.previousTapTime = motionEvent.getEventTime();
}
return true;
}
}