Revert gesture library update

This commit is contained in:
Leith Bade
2014-11-23 20:31:48 +11:00
parent db68e86a95
commit 8cb7a86d34
6 changed files with 967 additions and 12 deletions

View File

@@ -0,0 +1,161 @@
package com.almeros.android.multitouch.gesturedetectors;
import android.content.Context;
import android.view.MotionEvent;
/**
* @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie
* (code.almeros.com)
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
public abstract class BaseGestureDetector {
protected final Context mContext;
protected boolean mGestureInProgress;
protected MotionEvent mPrevEvent;
protected MotionEvent mCurrEvent;
protected float mCurrPressure;
protected float mPrevPressure;
protected long mTimeDelta;
/**
* This value is the threshold ratio between the previous combined pressure
* and the current combined pressure. When pressure decreases rapidly
* between events the position values can often be imprecise, as it usually
* indicates that the user is in the process of lifting a pointer off of the
* device. This value was tuned experimentally.
*/
protected static final float PRESSURE_THRESHOLD = 0.67f;
public BaseGestureDetector(Context context) {
mContext = context;
}
/**
* All gesture detectors need to be called through this method to be able to
* detect gestures. This method delegates work to handler methods
* (handleStartProgressEvent, handleInProgressEvent) implemented in
* extending classes.
*
* @param event
* @return
*/
public boolean onTouchEvent(MotionEvent event) {
final int actionCode = event.getAction() & MotionEvent.ACTION_MASK;
if (!mGestureInProgress) {
handleStartProgressEvent(actionCode, event);
} else {
handleInProgressEvent(actionCode, event);
}
return true;
}
/**
* Called when the current event occurred when NO gesture is in progress
* yet. The handling in this implementation may set the gesture in progress
* (via mGestureInProgress) or out of progress
*
* @param actionCode
* @param event
*/
protected abstract void handleStartProgressEvent(int actionCode,
MotionEvent event);
/**
* Called when the current event occurred when a gesture IS in progress. The
* handling in this implementation may set the gesture out of progress (via
* mGestureInProgress).
*
*
* @param actionCode
* @param event
*/
protected abstract void handleInProgressEvent(int actionCode,
MotionEvent event);
protected void updateStateByEvent(MotionEvent curr) {
final MotionEvent prev = mPrevEvent;
// Reset mCurrEvent
if (mCurrEvent != null) {
mCurrEvent.recycle();
mCurrEvent = null;
}
mCurrEvent = MotionEvent.obtain(curr);
// Delta time
mTimeDelta = curr.getEventTime() - prev.getEventTime();
// Pressure
mCurrPressure = curr.getPressure(curr.getActionIndex());
mPrevPressure = prev.getPressure(prev.getActionIndex());
}
protected void resetState() {
if (mPrevEvent != null) {
mPrevEvent.recycle();
mPrevEvent = null;
}
if (mCurrEvent != null) {
mCurrEvent.recycle();
mCurrEvent = null;
}
mGestureInProgress = false;
}
/**
* Returns {@code true} if a gesture is currently in progress.
*
* @return {@code true} if a gesture is currently in progress, {@code false}
* otherwise.
*/
public boolean isInProgress() {
return mGestureInProgress;
}
/**
* Return the time difference in milliseconds between the previous accepted
* GestureDetector event and the current GestureDetector event.
*
* @return Time difference since the last move event in milliseconds.
*/
public long getTimeDelta() {
return mTimeDelta;
}
/**
* Return the event time of the current GestureDetector event being
* processed.
*
* @return Current GestureDetector event time in milliseconds.
*/
public long getEventTime() {
return mCurrEvent.getEventTime();
}
}

View File

@@ -0,0 +1,185 @@
package com.almeros.android.multitouch.gesturedetectors;
import android.content.Context;
import android.graphics.PointF;
import android.view.MotionEvent;
/**
* @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie
* (code.almeros.com)
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
public class MoveGestureDetector extends BaseGestureDetector {
/**
* Listener which must be implemented which is used by MoveGestureDetector
* to perform callbacks to any implementing class which is registered to a
* MoveGestureDetector via the constructor.
*
* @see MoveGestureDetector.SimpleOnMoveGestureListener
*/
public interface OnMoveGestureListener {
public boolean onMove(MoveGestureDetector detector);
public boolean onMoveBegin(MoveGestureDetector detector);
public void onMoveEnd(MoveGestureDetector detector);
}
/**
* Helper class which may be extended and where the methods may be
* implemented. This way it is not necessary to implement all methods of
* OnMoveGestureListener.
*/
public static class SimpleOnMoveGestureListener implements
OnMoveGestureListener {
public boolean onMove(MoveGestureDetector detector) {
return false;
}
public boolean onMoveBegin(MoveGestureDetector detector) {
return true;
}
public void onMoveEnd(MoveGestureDetector detector) {
// Do nothing, overridden implementation may be used
}
}
private static final PointF FOCUS_DELTA_ZERO = new PointF();
private final OnMoveGestureListener mListener;
private PointF mFocusExternal = new PointF();
private PointF mFocusDeltaExternal = new PointF();
public MoveGestureDetector(Context context, OnMoveGestureListener listener) {
super(context);
mListener = listener;
}
@Override
protected void handleStartProgressEvent(int actionCode, MotionEvent event) {
switch (actionCode) {
case MotionEvent.ACTION_DOWN:
resetState(); // In case we missed an UP/CANCEL event
mPrevEvent = MotionEvent.obtain(event);
mTimeDelta = 0;
updateStateByEvent(event);
break;
case MotionEvent.ACTION_MOVE:
mGestureInProgress = mListener.onMoveBegin(this);
break;
}
}
@Override
protected void handleInProgressEvent(int actionCode, MotionEvent event) {
switch (actionCode) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mListener.onMoveEnd(this);
resetState();
break;
case MotionEvent.ACTION_MOVE:
updateStateByEvent(event);
// Only accept the event if our relative pressure is within
// a certain limit. This can help filter shaky data as a
// finger is lifted.
if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
final boolean updatePrevious = mListener.onMove(this);
if (updatePrevious) {
mPrevEvent.recycle();
mPrevEvent = MotionEvent.obtain(event);
}
}
break;
}
}
protected void updateStateByEvent(MotionEvent curr) {
super.updateStateByEvent(curr);
final MotionEvent prev = mPrevEvent;
// Focus intenal
PointF mCurrFocusInternal = determineFocalPoint(curr);
PointF mPrevFocusInternal = determineFocalPoint(prev);
// Focus external
// - Prevent skipping of focus delta when a finger is added or removed
boolean mSkipNextMoveEvent = prev.getPointerCount() != curr
.getPointerCount();
mFocusDeltaExternal = mSkipNextMoveEvent ? FOCUS_DELTA_ZERO
: new PointF(mCurrFocusInternal.x - mPrevFocusInternal.x,
mCurrFocusInternal.y - mPrevFocusInternal.y);
// - Don't directly use mFocusInternal (or skipping will occur). Add
// unskipped delta values to mFocusExternal instead.
mFocusExternal.x += mFocusDeltaExternal.x;
mFocusExternal.y += mFocusDeltaExternal.y;
}
/**
* Determine (multi)finger focal point (a.k.a. center point between all
* fingers)
*
* @param e
* @return PointF focal point
*/
private PointF determineFocalPoint(MotionEvent e) {
// Number of fingers on screen
final int pCount = e.getPointerCount();
float x = 0.0f;
float y = 0.0f;
for (int i = 0; i < pCount; i++) {
x += e.getX(i);
y += e.getY(i);
}
return new PointF(x / pCount, y / pCount);
}
public float getFocusX() {
return mFocusExternal.x;
}
public float getFocusY() {
return mFocusExternal.y;
}
public PointF getFocusDelta() {
return mFocusDeltaExternal;
}
}

View File

@@ -0,0 +1,180 @@
package com.almeros.android.multitouch.gesturedetectors;
import android.content.Context;
import android.view.MotionEvent;
/**
* @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie
* (code.almeros.com)
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
public class RotateGestureDetector extends TwoFingerGestureDetector {
/**
* Listener which must be implemented which is used by RotateGestureDetector
* to perform callbacks to any implementing class which is registered to a
* RotateGestureDetector via the constructor.
*
* @see RotateGestureDetector.SimpleOnRotateGestureListener
*/
public interface OnRotateGestureListener {
public boolean onRotate(RotateGestureDetector detector);
public boolean onRotateBegin(RotateGestureDetector detector);
public void onRotateEnd(RotateGestureDetector detector);
}
/**
* Helper class which may be extended and where the methods may be
* implemented. This way it is not necessary to implement all methods of
* OnRotateGestureListener.
*/
public static class SimpleOnRotateGestureListener implements
OnRotateGestureListener {
public boolean onRotate(RotateGestureDetector detector) {
return false;
}
public boolean onRotateBegin(RotateGestureDetector detector) {
return true;
}
public void onRotateEnd(RotateGestureDetector detector) {
// Do nothing, overridden implementation may be used
}
}
private final OnRotateGestureListener mListener;
private boolean mSloppyGesture;
public RotateGestureDetector(Context context,
OnRotateGestureListener listener) {
super(context);
mListener = listener;
}
@Override
protected void handleStartProgressEvent(int actionCode, MotionEvent event) {
switch (actionCode) {
case MotionEvent.ACTION_POINTER_DOWN:
// At least the second finger is on screen now
resetState(); // In case we missed an UP/CANCEL event
mPrevEvent = MotionEvent.obtain(event);
mTimeDelta = 0;
updateStateByEvent(event);
// See if we have a sloppy gesture
mSloppyGesture = isSloppyGesture(event);
if (!mSloppyGesture) {
// No, start gesture now
mGestureInProgress = mListener.onRotateBegin(this);
}
break;
case MotionEvent.ACTION_MOVE:
if (!mSloppyGesture) {
break;
}
// See if we still have a sloppy gesture
mSloppyGesture = isSloppyGesture(event);
if (!mSloppyGesture) {
// No, start normal gesture now
mGestureInProgress = mListener.onRotateBegin(this);
}
break;
case MotionEvent.ACTION_POINTER_UP:
if (!mSloppyGesture) {
break;
}
break;
}
}
@Override
protected void handleInProgressEvent(int actionCode, MotionEvent event) {
switch (actionCode) {
case MotionEvent.ACTION_POINTER_UP:
// Gesture ended but
updateStateByEvent(event);
if (!mSloppyGesture) {
mListener.onRotateEnd(this);
}
resetState();
break;
case MotionEvent.ACTION_CANCEL:
if (!mSloppyGesture) {
mListener.onRotateEnd(this);
}
resetState();
break;
case MotionEvent.ACTION_MOVE:
updateStateByEvent(event);
// Only accept the event if our relative pressure is within
// a certain limit. This can help filter shaky data as a
// finger is lifted.
if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
final boolean updatePrevious = mListener.onRotate(this);
if (updatePrevious) {
mPrevEvent.recycle();
mPrevEvent = MotionEvent.obtain(event);
}
}
break;
}
}
@Override
protected void resetState() {
super.resetState();
mSloppyGesture = false;
}
/**
* Return the rotation difference from the previous rotate event to the
* current event.
*
* @return The current rotation //difference in degrees.
*/
public float getRotationDegreesDelta() {
double diffRadians = Math.atan2(mPrevFingerDiffY, mPrevFingerDiffX)
- Math.atan2(mCurrFingerDiffY, mCurrFingerDiffX);
return (float) (diffRadians * 180.0 / Math.PI);
}
}

View File

@@ -0,0 +1,213 @@
package com.almeros.android.multitouch.gesturedetectors;
import android.content.Context;
import android.view.MotionEvent;
/**
* @author Robert Nordan (robert.nordan@norkart.no)
*
* Copyright (c) 2013, Norkart AS
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
public class ShoveGestureDetector extends TwoFingerGestureDetector {
/**
* Listener which must be implemented which is used by ShoveGestureDetector
* to perform callbacks to any implementing class which is registered to a
* ShoveGestureDetector via the constructor.
*
* @see ShoveGestureDetector.SimpleOnShoveGestureListener
*/
public interface OnShoveGestureListener {
public boolean onShove(ShoveGestureDetector detector);
public boolean onShoveBegin(ShoveGestureDetector detector);
public void onShoveEnd(ShoveGestureDetector detector);
}
/**
* Helper class which may be extended and where the methods may be
* implemented. This way it is not necessary to implement all methods of
* OnShoveGestureListener.
*/
public static class SimpleOnShoveGestureListener implements
OnShoveGestureListener {
public boolean onShove(ShoveGestureDetector detector) {
return false;
}
public boolean onShoveBegin(ShoveGestureDetector detector) {
return true;
}
public void onShoveEnd(ShoveGestureDetector detector) {
// Do nothing, overridden implementation may be used
}
}
private float mPrevAverageY;
private float mCurrAverageY;
private final OnShoveGestureListener mListener;
private boolean mSloppyGesture;
public ShoveGestureDetector(Context context, OnShoveGestureListener listener) {
super(context);
mListener = listener;
}
@Override
protected void handleStartProgressEvent(int actionCode, MotionEvent event) {
switch (actionCode) {
case MotionEvent.ACTION_POINTER_DOWN:
// At least the second finger is on screen now
resetState(); // In case we missed an UP/CANCEL event
mPrevEvent = MotionEvent.obtain(event);
mTimeDelta = 0;
updateStateByEvent(event);
// See if we have a sloppy gesture
mSloppyGesture = isSloppyGesture(event);
if (!mSloppyGesture) {
// No, start gesture now
mGestureInProgress = mListener.onShoveBegin(this);
}
break;
case MotionEvent.ACTION_MOVE:
if (!mSloppyGesture) {
break;
}
// See if we still have a sloppy gesture
mSloppyGesture = isSloppyGesture(event);
if (!mSloppyGesture) {
// No, start normal gesture now
mGestureInProgress = mListener.onShoveBegin(this);
}
break;
case MotionEvent.ACTION_POINTER_UP:
if (!mSloppyGesture) {
break;
}
break;
}
}
@Override
protected void handleInProgressEvent(int actionCode, MotionEvent event) {
switch (actionCode) {
case MotionEvent.ACTION_POINTER_UP:
// Gesture ended but
updateStateByEvent(event);
if (!mSloppyGesture) {
mListener.onShoveEnd(this);
}
resetState();
break;
case MotionEvent.ACTION_CANCEL:
if (!mSloppyGesture) {
mListener.onShoveEnd(this);
}
resetState();
break;
case MotionEvent.ACTION_MOVE:
updateStateByEvent(event);
// Only accept the event if our relative pressure is within
// a certain limit. This can help filter shaky data as a
// finger is lifted. Also check that shove is meaningful.
if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD
&& Math.abs(getShovePixelsDelta()) > 0.5f) {
final boolean updatePrevious = mListener.onShove(this);
if (updatePrevious) {
mPrevEvent.recycle();
mPrevEvent = MotionEvent.obtain(event);
}
}
break;
}
}
@Override
protected void resetState() {
super.resetState();
mSloppyGesture = false;
mPrevAverageY = 0.0f;
mCurrAverageY = 0.0f;
}
@Override
protected void updateStateByEvent(MotionEvent curr) {
super.updateStateByEvent(curr);
final MotionEvent prev = mPrevEvent;
float py0 = prev.getY(0);
float py1 = prev.getY(1);
mPrevAverageY = (py0 + py1) / 2.0f;
float cy0 = curr.getY(0);
float cy1 = curr.getY(1);
mCurrAverageY = (cy0 + cy1) / 2.0f;
}
@Override
protected boolean isSloppyGesture(MotionEvent event) {
boolean sloppy = super.isSloppyGesture(event);
if (sloppy)
return true;
// If it's not traditionally sloppy, we check if the angle between
// fingers
// is acceptable.
double angle = Math.abs(Math.atan2(mCurrFingerDiffY, mCurrFingerDiffX));
// about 20 degrees, left or right
return !((0.0f < angle && angle < 0.35f) || 2.79f < angle
&& angle < Math.PI);
}
/**
* Return the distance in pixels from the previous shove event to the
* current event.
*
* @return The current distance in pixels.
*/
public float getShovePixelsDelta() {
return mCurrAverageY - mPrevAverageY;
}
}

View File

@@ -0,0 +1,223 @@
package com.almeros.android.multitouch.gesturedetectors;
import android.content.Context;
import android.graphics.PointF;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
/**
* @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie
* (code.almeros.com)
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
public abstract class TwoFingerGestureDetector extends BaseGestureDetector {
private final float mEdgeSlop;
protected float mPrevFingerDiffX;
protected float mPrevFingerDiffY;
protected float mCurrFingerDiffX;
protected float mCurrFingerDiffY;
private float mCurrLen;
private float mPrevLen;
private PointF mFocus;
public TwoFingerGestureDetector(Context context) {
super(context);
ViewConfiguration config = ViewConfiguration.get(context);
mEdgeSlop = config.getScaledEdgeSlop();
}
@Override
protected abstract void handleStartProgressEvent(int actionCode,
MotionEvent event);
@Override
protected abstract void handleInProgressEvent(int actionCode,
MotionEvent event);
protected void updateStateByEvent(MotionEvent curr) {
super.updateStateByEvent(curr);
final MotionEvent prev = mPrevEvent;
mCurrLen = -1;
mPrevLen = -1;
// Previous
final float px0 = prev.getX(0);
final float py0 = prev.getY(0);
final float px1 = prev.getX(1);
final float py1 = prev.getY(1);
final float pvx = px1 - px0;
final float pvy = py1 - py0;
mPrevFingerDiffX = pvx;
mPrevFingerDiffY = pvy;
// Current
final float cx0 = curr.getX(0);
final float cy0 = curr.getY(0);
final float cx1 = curr.getX(1);
final float cy1 = curr.getY(1);
final float cvx = cx1 - cx0;
final float cvy = cy1 - cy0;
mCurrFingerDiffX = cvx;
mCurrFingerDiffY = cvy;
mFocus = determineFocalPoint(curr);
}
/**
* Return the current distance between the two pointers forming the gesture
* in progress.
*
* @return Distance between pointers in pixels.
*/
public float getCurrentSpan() {
if (mCurrLen == -1) {
final float cvx = mCurrFingerDiffX;
final float cvy = mCurrFingerDiffY;
mCurrLen = (float) Math.sqrt(cvx * cvx + cvy * cvy);
}
return mCurrLen;
}
/**
* Return the previous distance between the two pointers forming the gesture
* in progress.
*
* @return Previous distance between pointers in pixels.
*/
public float getPreviousSpan() {
if (mPrevLen == -1) {
final float pvx = mPrevFingerDiffX;
final float pvy = mPrevFingerDiffY;
mPrevLen = (float) Math.sqrt(pvx * pvx + pvy * pvy);
}
return mPrevLen;
}
/**
* MotionEvent has no getRawX(int) method; simulate it pending future API
* approval.
*
* @param event
* @param pointerIndex
* @return
*/
protected static float getRawX(MotionEvent event, int pointerIndex) {
float offset = event.getX() - event.getRawX();
if (pointerIndex < event.getPointerCount()) {
return event.getX(pointerIndex) + offset;
}
return 0.0f;
}
/**
* MotionEvent has no getRawY(int) method; simulate it pending future API
* approval.
*
* @param event
* @param pointerIndex
* @return
*/
protected static float getRawY(MotionEvent event, int pointerIndex) {
float offset = event.getY() - event.getRawY();
if (pointerIndex < event.getPointerCount()) {
return event.getY(pointerIndex) + offset;
}
return 0.0f;
}
/**
* Check if we have a sloppy gesture. Sloppy gestures can happen if the edge
* of the user's hand is touching the screen, for example.
*
* @param event
* @return
*/
protected boolean isSloppyGesture(MotionEvent event) {
// As orientation can change, query the metrics in touch down
DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
float mRightSlopEdge = metrics.widthPixels - mEdgeSlop;
float mBottomSlopEdge = metrics.heightPixels - mEdgeSlop;
final float edgeSlop = mEdgeSlop;
final float x0 = event.getRawX();
final float y0 = event.getRawY();
final float x1 = getRawX(event, 1);
final float y1 = getRawY(event, 1);
boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop || x0 > mRightSlopEdge
|| y0 > mBottomSlopEdge;
boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop || x1 > mRightSlopEdge
|| y1 > mBottomSlopEdge;
if (p0sloppy && p1sloppy) {
return true;
} else if (p0sloppy) {
return true;
} else if (p1sloppy) {
return true;
}
return false;
}
/**
* Determine (multi)finger focal point (a.k.a. center point between all
* fingers)
*
* @param e
* @return PointF focal point
*/
public static PointF determineFocalPoint(MotionEvent e) {
// Number of fingers on screen
final int pCount = e.getPointerCount();
float x = 0.0f;
float y = 0.0f;
for (int i = 0; i < pCount; i++) {
x += e.getX(i);
y += e.getY(i);
}
return new PointF(x / pCount, y / pCount);
}
public float getFocusX() {
return mFocus.x;
}
public float getFocusY() {
return mFocus.y;
}
}

View File

@@ -29,8 +29,8 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.widget.ZoomButtonsController;
import com.almeros.android.multitouch.MoveGestureDetector;
import com.almeros.android.multitouch.RotateGestureDetector;
import com.almeros.android.multitouch.gesturedetectors.RotateGestureDetector;
import com.almeros.android.multitouch.gesturedetectors.TwoFingerGestureDetector;
import java.util.ArrayList;
import java.util.List;
@@ -74,7 +74,6 @@ public class MapView extends SurfaceView {
private GestureDetectorCompat mGestureDetector;
private ScaleGestureDetector mScaleGestureDetector;
private RotateGestureDetector mRotateGestureDetector;
private MoveGestureDetector mMoveGestureDetector;
private boolean mTwoTap = false;
// Shows zoom buttons
@@ -183,7 +182,6 @@ public class MapView extends SurfaceView {
mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureListener());
ScaleGestureDetectorCompat.setQuickScaleEnabled(mScaleGestureDetector, true);
mRotateGestureDetector = new RotateGestureDetector(context, new RotateGestureListener());
mMoveGestureDetector = new MoveGestureDetector(context, new MoveGestureListener());
// Shows the zoom controls
// But not when in Eclipse UI editor
@@ -587,7 +585,6 @@ public class MapView extends SurfaceView {
}
// Check two finger gestures first
mMoveGestureDetector.onTouchEvent(event);
mRotateGestureDetector.onTouchEvent(event);
mScaleGestureDetector.onTouchEvent(event);
@@ -613,7 +610,8 @@ public class MapView extends SurfaceView {
boolean inProgress = mRotateGestureDetector.isInProgress() || mScaleGestureDetector.isInProgress();
if (mTwoTap && isTap && !inProgress) {
zoom(false, mMoveGestureDetector.getFocusX(), mMoveGestureDetector.getFocusY());
PointF focalPoint = TwoFingerGestureDetector.determineFocalPoint(event);
zoom(false, focalPoint.x, focalPoint.y);
mTwoTap = false;
return true;
}
@@ -851,17 +849,12 @@ public class MapView extends SurfaceView {
// Rotate the map
double bearing = mNativeMapView.getBearing();
bearing += detector.getRotationDegreesDelta();
mNativeMapView.setBearing(bearing, mMoveGestureDetector.getFocusX() / mScreenDensity, mMoveGestureDetector.getFocusY() / mScreenDensity);
mNativeMapView.setBearing(bearing, detector.getFocusX() / mScreenDensity, detector.getFocusY() / mScreenDensity);
return true;
}
}
// This class handles two finger moves during rotation
private class MoveGestureListener extends MoveGestureDetector.SimpleOnMoveGestureListener {
}
// This class handles input events from the zoom control buttons
// Zoom controls allow single touch only devices to zoom in and out
private class OnZoomListener implements ZoomButtonsController.OnZoomListener {