// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.android.stardroid.touch;
//import android.util.Log;
import android.util.Log;
import android.view.MotionEvent;
import com.google.android.stardroid.util.MathUtil;
import com.google.android.stardroid.util.MiscUtil;
/**
* Detects map drags, rotations and pinch zooms.
*
* @author John Taylor
*/
public class DragRotateZoomGestureDetector {
/**
* Listens for the gestures detected by the {@link DragRotateZoomGestureDetector}.
*
* @author John Taylor
*/
public interface DragRotateZoomGestureDetectorListener {
boolean onDrag(float xPixels, float yPixels);
boolean onStretch(float ratio);
boolean onRotate(float radians);
}
private static final String TAG = MiscUtil.getTag(DragRotateZoomGestureDetector.class);
private DragRotateZoomGestureDetectorListener listener;;
public DragRotateZoomGestureDetector(DragRotateZoomGestureDetectorListener listener) {
this.listener = listener;
}
private enum State {READY, DRAGGING, DRAGGING2}
private State currentState = State.READY;
private float last1X;
private float last1Y;
private float last2X;
private float last2Y;
public boolean onTouchEvent(MotionEvent ev) {
// The state changes are as follows.
// READY -> DRAGGING -> DRAGGING2 -> READY
//
// ACTION_DOWN: READY->DRAGGING
// last position = current position
//
// ACTION_MOVE: no state change
// calculate move = current position - last position
// last position = current position
//
// ACTION_UP: DRAGGING->READY
// last position = null
// ...or...from DRAGGING
//
// ACTION_POINTER_DOWN: DRAGGING->DRAGGING2
// we're in multitouch mode
// last position1 = current position1
// last poisiton2 = current position2
//
// ACTION_MOVE:
// calculate move
// last position1 = current position1
// last position2 = current position2
int actionCode = ev.getAction() & MotionEvent.ACTION_MASK;
// Log.d(TAG, "Action: " + actionCode + ", current state " + currentState);
if (actionCode == MotionEvent.ACTION_DOWN || currentState == State.READY) {
currentState = State.DRAGGING;
last1X = ev.getX();
last1Y = ev.getY();
// Log.d(TAG, "Down. Store last position " + last1X + ", " + last1Y);
return true;
}
if (actionCode == MotionEvent.ACTION_MOVE && currentState == State.DRAGGING) {
// Log.d(TAG, "Move");
float current1X = ev.getX();
float current1Y = ev.getY();
// Log.d(TAG, "Move. Last position " + last1X + ", " + last1Y +
// "Current position " + current1X + ", " + current1Y);
listener.onDrag(current1X - last1X, current1Y - last1Y);
last1X = current1X;
last1Y = current1Y;
return true;
}
if (actionCode == MotionEvent.ACTION_MOVE && currentState == State.DRAGGING2) {
// Log.d(TAG, "Move with two fingers");
int pointerCount = ev.getPointerCount();
if (pointerCount != 2) {
Log.w(TAG, "Expected exactly two pointers but got " + pointerCount);
return false;
}
float current1X = ev.getX(0);
float current1Y = ev.getY(0);
float current2X = ev.getX(1);
float current2Y = ev.getY(1);
// Log.d(TAG, "Old Point 1: " + lastPointer1X + ", " + lastPointer1Y);
// Log.d(TAG, "Old Point 2: " + lastPointer2X + ", " + lastPointer2Y);
// Log.d(TAG, "New Point 1: " + current1X + ", " + current1Y);
// Log.d(TAG, "New Point 2: " + current2X + ", " + current2Y);
float distanceMovedX1 = current1X - last1X;
float distanceMovedY1 = current1Y - last1Y;
float distanceMovedX2 = current2X - last2X;
float distanceMovedY2 = current2Y - last2Y;
// Log.d(TAG, "Point 1 moved by: " + distanceMovedX1 + ", " + distanceMovedY1);
// Log.d(TAG, "Point 2 moved by: " + distanceMovedX2 + ", " + distanceMovedY2);
// Dragging map by the mean of the points
listener.onDrag((distanceMovedX1 + distanceMovedX2) / 2,
(distanceMovedY1 + distanceMovedY2) / 2);
// These are the vectors between the two points.
float vectorLastX = last1X - last2X;
float vectorLastY = last1Y - last2Y;
float vectorCurrentX = current1X - current2X;
float vectorCurrentY = current1Y - current2Y;
// Log.d(TAG, "Previous vector: " + vectorBeforeX + ", " + vectorBeforeY);
// Log.d(TAG, "Current vector: " + vectorCurrentX + ", " + vectorCurrentY);
float lengthRatio = MathUtil.sqrt(normSquared(vectorCurrentX, vectorCurrentY)
/ normSquared(vectorLastX, vectorLastY));
// Log.d(TAG, "Stretching map by ratio " + ratio);
listener.onStretch(lengthRatio);
float angleLast = MathUtil.atan2(vectorLastX, vectorLastY);
float angleCurrent = MathUtil.atan2(vectorCurrentX, vectorCurrentY);
// Log.d(TAG, "Angle before " + angleBefore);
// Log.d(TAG, "Angle after " + angleAfter);
float angleDelta = angleCurrent - angleLast;
// Log.d(TAG, "Rotating map by angle delta " + angleDelta);
listener.onRotate(angleDelta * MathUtil.RADIANS_TO_DEGREES);
last1X = current1X;
last1Y = current1Y;
last2X = current2X;
last2Y = current2Y;
return true;
}
if (actionCode == MotionEvent.ACTION_UP && currentState != State.READY) {
// Log.d(TAG, "Up");
currentState = State.READY;
return true;
}
if (actionCode == MotionEvent.ACTION_POINTER_DOWN && currentState == State.DRAGGING) {
//Log.d(TAG, "Non primary pointer down " + pointer);
int pointerCount = ev.getPointerCount();
if (pointerCount != 2) {
Log.w(TAG, "Expected exactly two pointers but got " + pointerCount);
return false;
}
currentState = State.DRAGGING2;
last1X = ev.getX(0);
last1Y = ev.getY(0);
last2X = ev.getX(1);
last2Y = ev.getY(1);
return true;
}
if (actionCode == MotionEvent.ACTION_POINTER_UP && currentState == State.DRAGGING2) {
// Log.d(TAG, "Non primary pointer up " + pointer);
// Let's just drop dragging for now - can worry about continuity with one finger
// drag later.
currentState = State.READY;
return true;
}
// Log.d(TAG, "End state " + currentState);
return false;
}
private static float normSquared(float x, float y) {
return (x * x + y * y);
}
}