/* * 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.core.model.GeoPoint; import android.content.Context; import android.view.MotionEvent; import android.view.ScaleGestureDetector; /** * Implementation for multi-touch capable devices. */ public class MultiTouchHandler extends TouchEventHandler { private static final int INVALID_POINTER_ID = -1; private int activePointerId; private long multiTouchDownTime; private final ScaleGestureDetector scaleGestureDetector; MultiTouchHandler(Context context, MapView mapView) { super(context, mapView); this.activePointerId = INVALID_POINTER_ID; this.scaleGestureDetector = new ScaleGestureDetector(context, new ScaleListener(this.mapView)); } @Override public int getAction(MotionEvent motionEvent) { return motionEvent.getAction() & MotionEvent.ACTION_MASK; } private boolean onActionCancel() { this.longPressDetector.pressStop(); this.activePointerId = INVALID_POINTER_ID; return true; } private boolean onActionDown(MotionEvent motionEvent) { this.longPressDetector.pressStart(); this.previousPositionX = motionEvent.getX(); this.previousPositionY = motionEvent.getY(); this.moveThresholdReached = false; // save the ID of the pointer this.activePointerId = motionEvent.getPointerId(0); return true; } private boolean onActionMove(MotionEvent motionEvent) { int pointerIndex = motionEvent.findPointerIndex(this.activePointerId); if (this.scaleGestureDetector.isInProgress()) { return true; } // calculate the distance between previous and current position float moveX = motionEvent.getX(pointerIndex) - this.previousPositionX; float moveY = motionEvent.getY(pointerIndex) - this.previousPositionY; if (!this.moveThresholdReached) { if (Math.abs(moveX) > this.mapMoveDelta || Math.abs(moveY) > this.mapMoveDelta) { // the map movement threshold has been reached this.longPressDetector.pressStop(); this.moveThresholdReached = true; // save the position of the event this.previousPositionX = motionEvent.getX(pointerIndex); this.previousPositionY = motionEvent.getY(pointerIndex); } return true; } // save the position of the event this.previousPositionX = motionEvent.getX(pointerIndex); this.previousPositionY = motionEvent.getY(pointerIndex); this.mapView.getFrameBuffer().matrixPostTranslate(moveX, moveY); this.mapView.getMapPosition().moveMap(moveX, moveY); this.mapView.redrawTiles(); return true; } private boolean onActionPointerDown(MotionEvent motionEvent) { this.longPressDetector.pressStop(); this.multiTouchDownTime = motionEvent.getEventTime(); return true; } private boolean onActionPointerUp(MotionEvent motionEvent) { this.longPressDetector.pressStop(); // 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; } // save the position of the event this.previousPositionX = motionEvent.getX(pointerIndex); this.previousPositionY = motionEvent.getY(pointerIndex); this.activePointerId = motionEvent.getPointerId(pointerIndex); } // calculate the time difference since the pointer has gone down long multiTouchTime = motionEvent.getEventTime() - this.multiTouchDownTime; if (multiTouchTime < this.doubleTapTimeout) { // multi-touch tap event, zoom out this.previousEventTap = false; this.mapView.zoom((byte) -1, 1); } return true; } private boolean onActionUp(MotionEvent motionEvent) { this.longPressDetector.pressStop(); int pointerIndex = motionEvent.findPointerIndex(this.activePointerId); this.activePointerId = INVALID_POINTER_ID; if (this.moveThresholdReached || this.longPressDetector.isEventHandled()) { this.previousEventTap = false; } else { if (this.previousEventTap) { // calculate the distance to the previous tap position float tapDiffX = Math.abs(motionEvent.getX(pointerIndex) - this.previousTapX); float tapDiffY = Math.abs(motionEvent.getY(pointerIndex) - this.previousTapY); long tapDiffTime = motionEvent.getEventTime() - this.previousTapTime; // check if a double-tap event occurred if (tapDiffX < this.doubleTapDelta && tapDiffY < this.doubleTapDelta && tapDiffTime < this.doubleTapTimeout) { // double-tap event, zoom in this.previousEventTap = false; this.mapView.setCenter(this.mapView.getProjection().fromPixels((int) motionEvent.getX(), (int) motionEvent.getY())); this.mapView.zoom((byte) 1, 1); return true; } } else { this.previousEventTap = true; } // store the position and the time of this tap event this.previousTapX = motionEvent.getX(pointerIndex); this.previousTapY = motionEvent.getY(pointerIndex); this.previousTapTime = motionEvent.getEventTime(); GeoPoint tapPoint = this.mapView.getProjection().fromPixels((int) motionEvent.getX(pointerIndex), (int) motionEvent.getY(pointerIndex)); if (tapPoint != null) { synchronized (this.mapView.getOverlays()) { for (int i = this.mapView.getOverlays().size() - 1; i >= 0; --i) { if (this.mapView.getOverlays().get(i).onTap(tapPoint, this.mapView)) { // the tap event has been handled break; } } } } } return true; } @Override boolean handleMotionEvent(MotionEvent motionEvent) { // workaround for a bug in the ScaleGestureDetector, see Android issue #12976 if (motionEvent.getAction() != MotionEvent.ACTION_MOVE || motionEvent.getPointerCount() > 1) { this.scaleGestureDetector.onTouchEvent(motionEvent); } int action = getAction(motionEvent); if (action == MotionEvent.ACTION_DOWN) { return onActionDown(motionEvent); } else if (action == MotionEvent.ACTION_MOVE) { return onActionMove(motionEvent); } else if (action == MotionEvent.ACTION_UP) { return onActionUp(motionEvent); } else if (action == MotionEvent.ACTION_CANCEL) { return onActionCancel(); } else if (action == MotionEvent.ACTION_POINTER_DOWN) { return onActionPointerDown(motionEvent); } else if (action == MotionEvent.ACTION_POINTER_UP) { return onActionPointerUp(motionEvent); } // the event was not handled return false; } }