Merge "New implementation for ScaleGestureDetector" into jb-mr1-dev
This commit is contained in:
@ -17,14 +17,13 @@
|
||||
package android.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.FloatMath;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Detects transformation gestures involving more than one pointer ("multitouch")
|
||||
* using the supplied {@link MotionEvent}s. The {@link OnScaleGestureListener}
|
||||
* callback will notify users when a particular gesture event has occurred.
|
||||
* Detects scaling transformation gestures using the supplied {@link MotionEvent}s.
|
||||
* The {@link OnScaleGestureListener} callback will notify users when a particular
|
||||
* gesture event has occurred.
|
||||
*
|
||||
* This class should only be used with {@link MotionEvent}s reported via touch.
|
||||
*
|
||||
* To use this class:
|
||||
@ -121,43 +120,21 @@ public class ScaleGestureDetector {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This value is the threshold ratio between our previous combined pressure
|
||||
* and the current combined pressure. We will only fire an onScale event if
|
||||
* the computed ratio between the current and previous event pressures is
|
||||
* greater than this value. 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.
|
||||
* Its value was tuned experimentally.
|
||||
*/
|
||||
private static final float PRESSURE_THRESHOLD = 0.67f;
|
||||
|
||||
private final Context mContext;
|
||||
private final OnScaleGestureListener mListener;
|
||||
private boolean mGestureInProgress;
|
||||
|
||||
private MotionEvent mPrevEvent;
|
||||
private MotionEvent mCurrEvent;
|
||||
|
||||
private float mFocusX;
|
||||
private float mFocusY;
|
||||
private float mPrevFingerDiffX;
|
||||
private float mPrevFingerDiffY;
|
||||
private float mCurrFingerDiffX;
|
||||
private float mCurrFingerDiffY;
|
||||
private float mCurrLen;
|
||||
private float mPrevLen;
|
||||
private float mScaleFactor;
|
||||
private float mCurrPressure;
|
||||
private float mPrevPressure;
|
||||
private long mTimeDelta;
|
||||
|
||||
private boolean mInvalidGesture;
|
||||
|
||||
// Pointer IDs currently responsible for the two fingers controlling the gesture
|
||||
private int mActiveId0;
|
||||
private int mActiveId1;
|
||||
private boolean mActive0MostRecent;
|
||||
private float mCurrSpan;
|
||||
private float mPrevSpan;
|
||||
private float mCurrSpanX;
|
||||
private float mCurrSpanY;
|
||||
private float mPrevSpanX;
|
||||
private float mPrevSpanY;
|
||||
private long mCurrTime;
|
||||
private long mPrevTime;
|
||||
private boolean mInProgress;
|
||||
|
||||
/**
|
||||
* Consistency verifier for debugging purposes.
|
||||
@ -171,6 +148,18 @@ public class ScaleGestureDetector {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener}
|
||||
* when appropriate.
|
||||
*
|
||||
* <p>Applications should pass a complete and consistent event stream to this method.
|
||||
* A complete and consistent event stream involves all MotionEvents from the initial
|
||||
* ACTION_DOWN to the final ACTION_UP or ACTION_CANCEL.</p>
|
||||
*
|
||||
* @param event The event to process
|
||||
* @return true if the event was processed and the detector wants to receive the
|
||||
* rest of the MotionEvents in this event stream.
|
||||
*/
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (mInputEventConsistencyVerifier != null) {
|
||||
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
|
||||
@ -178,265 +167,110 @@ public class ScaleGestureDetector {
|
||||
|
||||
final int action = event.getActionMasked();
|
||||
|
||||
if (action == MotionEvent.ACTION_DOWN) {
|
||||
reset(); // Start fresh
|
||||
}
|
||||
|
||||
boolean handled = true;
|
||||
if (mInvalidGesture) {
|
||||
handled = false;
|
||||
} else if (!mGestureInProgress) {
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
mActiveId0 = event.getPointerId(0);
|
||||
mActive0MostRecent = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
reset();
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_POINTER_DOWN: {
|
||||
// We have a new multi-finger gesture
|
||||
if (mPrevEvent != null) mPrevEvent.recycle();
|
||||
mPrevEvent = MotionEvent.obtain(event);
|
||||
mTimeDelta = 0;
|
||||
|
||||
int index1 = event.getActionIndex();
|
||||
int index0 = event.findPointerIndex(mActiveId0);
|
||||
mActiveId1 = event.getPointerId(index1);
|
||||
if (index0 < 0 || index0 == index1) {
|
||||
// Probably someone sending us a broken event stream.
|
||||
index0 = findNewActiveIndex(event, mActiveId1, -1);
|
||||
mActiveId0 = event.getPointerId(index0);
|
||||
}
|
||||
mActive0MostRecent = false;
|
||||
|
||||
setContext(event);
|
||||
|
||||
mGestureInProgress = mListener.onScaleBegin(this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Transform gesture in progress - attempt to handle it
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_POINTER_DOWN: {
|
||||
// End the old gesture and begin a new one with the most recent two fingers.
|
||||
mListener.onScaleEnd(this);
|
||||
final int oldActive0 = mActiveId0;
|
||||
final int oldActive1 = mActiveId1;
|
||||
reset();
|
||||
|
||||
mPrevEvent = MotionEvent.obtain(event);
|
||||
mActiveId0 = mActive0MostRecent ? oldActive0 : oldActive1;
|
||||
mActiveId1 = event.getPointerId(event.getActionIndex());
|
||||
mActive0MostRecent = false;
|
||||
|
||||
int index0 = event.findPointerIndex(mActiveId0);
|
||||
if (index0 < 0 || mActiveId0 == mActiveId1) {
|
||||
// Probably someone sending us a broken event stream.
|
||||
Log.e(TAG, "Got " + MotionEvent.actionToString(action) +
|
||||
" with bad state while a gesture was in progress. " +
|
||||
"Did you forget to pass an event to " +
|
||||
"ScaleGestureDetector#onTouchEvent?");
|
||||
index0 = findNewActiveIndex(event, mActiveId1, -1);
|
||||
mActiveId0 = event.getPointerId(index0);
|
||||
}
|
||||
|
||||
setContext(event);
|
||||
|
||||
mGestureInProgress = mListener.onScaleBegin(this);
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_POINTER_UP: {
|
||||
final int pointerCount = event.getPointerCount();
|
||||
final int actionIndex = event.getActionIndex();
|
||||
final int actionId = event.getPointerId(actionIndex);
|
||||
|
||||
boolean gestureEnded = false;
|
||||
if (pointerCount > 2) {
|
||||
if (actionId == mActiveId0) {
|
||||
final int newIndex = findNewActiveIndex(event, mActiveId1, actionIndex);
|
||||
if (newIndex >= 0) {
|
||||
mListener.onScaleEnd(this);
|
||||
mActiveId0 = event.getPointerId(newIndex);
|
||||
mActive0MostRecent = true;
|
||||
mPrevEvent = MotionEvent.obtain(event);
|
||||
setContext(event);
|
||||
mGestureInProgress = mListener.onScaleBegin(this);
|
||||
} else {
|
||||
gestureEnded = true;
|
||||
}
|
||||
} else if (actionId == mActiveId1) {
|
||||
final int newIndex = findNewActiveIndex(event, mActiveId0, actionIndex);
|
||||
if (newIndex >= 0) {
|
||||
mListener.onScaleEnd(this);
|
||||
mActiveId1 = event.getPointerId(newIndex);
|
||||
mActive0MostRecent = false;
|
||||
mPrevEvent = MotionEvent.obtain(event);
|
||||
setContext(event);
|
||||
mGestureInProgress = mListener.onScaleBegin(this);
|
||||
} else {
|
||||
gestureEnded = true;
|
||||
}
|
||||
}
|
||||
mPrevEvent.recycle();
|
||||
mPrevEvent = MotionEvent.obtain(event);
|
||||
setContext(event);
|
||||
} else {
|
||||
gestureEnded = true;
|
||||
}
|
||||
|
||||
if (gestureEnded) {
|
||||
// Gesture ended
|
||||
setContext(event);
|
||||
|
||||
// Set focus point to the remaining finger
|
||||
final int activeId = actionId == mActiveId0 ? mActiveId1 : mActiveId0;
|
||||
final int index = event.findPointerIndex(activeId);
|
||||
mFocusX = event.getX(index);
|
||||
mFocusY = event.getY(index);
|
||||
|
||||
mListener.onScaleEnd(this);
|
||||
reset();
|
||||
mActiveId0 = activeId;
|
||||
mActive0MostRecent = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
mListener.onScaleEnd(this);
|
||||
reset();
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
reset();
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
setContext(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.onScale(this);
|
||||
|
||||
if (updatePrevious) {
|
||||
mPrevEvent.recycle();
|
||||
mPrevEvent = MotionEvent.obtain(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!handled && mInputEventConsistencyVerifier != null) {
|
||||
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
private int findNewActiveIndex(MotionEvent ev, int otherActiveId, int removedPointerIndex) {
|
||||
final int pointerCount = ev.getPointerCount();
|
||||
|
||||
// It's ok if this isn't found and returns -1, it simply won't match.
|
||||
final int otherActiveIndex = ev.findPointerIndex(otherActiveId);
|
||||
|
||||
// Pick a new id and update tracking state.
|
||||
for (int i = 0; i < pointerCount; i++) {
|
||||
if (i != removedPointerIndex && i != otherActiveIndex) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void setContext(MotionEvent curr) {
|
||||
if (mCurrEvent != null) {
|
||||
mCurrEvent.recycle();
|
||||
}
|
||||
mCurrEvent = MotionEvent.obtain(curr);
|
||||
|
||||
mCurrLen = -1;
|
||||
mPrevLen = -1;
|
||||
mScaleFactor = -1;
|
||||
|
||||
final MotionEvent prev = mPrevEvent;
|
||||
|
||||
final int prevIndex0 = prev.findPointerIndex(mActiveId0);
|
||||
final int prevIndex1 = prev.findPointerIndex(mActiveId1);
|
||||
final int currIndex0 = curr.findPointerIndex(mActiveId0);
|
||||
final int currIndex1 = curr.findPointerIndex(mActiveId1);
|
||||
|
||||
if (prevIndex0 < 0 || prevIndex1 < 0 || currIndex0 < 0 || currIndex1 < 0) {
|
||||
mInvalidGesture = true;
|
||||
Log.e(TAG, "Invalid MotionEvent stream detected.", new Throwable());
|
||||
if (mGestureInProgress) {
|
||||
final boolean streamComplete = action == MotionEvent.ACTION_UP ||
|
||||
action == MotionEvent.ACTION_CANCEL;
|
||||
if (action == MotionEvent.ACTION_DOWN || streamComplete) {
|
||||
// Reset any scale in progress with the listener.
|
||||
// If it's an ACTION_DOWN we're beginning a new event stream.
|
||||
// This means the app probably didn't give us all the events. Shame on it.
|
||||
if (mInProgress) {
|
||||
mListener.onScaleEnd(this);
|
||||
mInProgress = false;
|
||||
}
|
||||
|
||||
if (streamComplete) {
|
||||
return true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final float px0 = prev.getX(prevIndex0);
|
||||
final float py0 = prev.getY(prevIndex0);
|
||||
final float px1 = prev.getX(prevIndex1);
|
||||
final float py1 = prev.getY(prevIndex1);
|
||||
final float cx0 = curr.getX(currIndex0);
|
||||
final float cy0 = curr.getY(currIndex0);
|
||||
final float cx1 = curr.getX(currIndex1);
|
||||
final float cy1 = curr.getY(currIndex1);
|
||||
final boolean configChanged =
|
||||
action == MotionEvent.ACTION_POINTER_UP ||
|
||||
action == MotionEvent.ACTION_POINTER_DOWN;
|
||||
final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
|
||||
final int skipIndex = pointerUp ? event.getActionIndex() : -1;
|
||||
|
||||
final float pvx = px1 - px0;
|
||||
final float pvy = py1 - py0;
|
||||
final float cvx = cx1 - cx0;
|
||||
final float cvy = cy1 - cy0;
|
||||
mPrevFingerDiffX = pvx;
|
||||
mPrevFingerDiffY = pvy;
|
||||
mCurrFingerDiffX = cvx;
|
||||
mCurrFingerDiffY = cvy;
|
||||
|
||||
mFocusX = cx0 + cvx * 0.5f;
|
||||
mFocusY = cy0 + cvy * 0.5f;
|
||||
mTimeDelta = curr.getEventTime() - prev.getEventTime();
|
||||
mCurrPressure = curr.getPressure(currIndex0) + curr.getPressure(currIndex1);
|
||||
mPrevPressure = prev.getPressure(prevIndex0) + prev.getPressure(prevIndex1);
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
if (mPrevEvent != null) {
|
||||
mPrevEvent.recycle();
|
||||
mPrevEvent = null;
|
||||
// Determine focal point
|
||||
float sumX = 0, sumY = 0;
|
||||
final int count = event.getPointerCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (skipIndex == i) continue;
|
||||
sumX += event.getX(i);
|
||||
sumY += event.getY(i);
|
||||
}
|
||||
if (mCurrEvent != null) {
|
||||
mCurrEvent.recycle();
|
||||
mCurrEvent = null;
|
||||
final int div = pointerUp ? count - 1 : count;
|
||||
final float focusX = sumX / div;
|
||||
final float focusY = sumY / div;
|
||||
|
||||
// Determine average deviation from focal point
|
||||
float devSumX = 0, devSumY = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (skipIndex == i) continue;
|
||||
devSumX += Math.abs(event.getX(i) - focusX);
|
||||
devSumY += Math.abs(event.getY(i) - focusY);
|
||||
}
|
||||
mGestureInProgress = false;
|
||||
mActiveId0 = -1;
|
||||
mActiveId1 = -1;
|
||||
mInvalidGesture = false;
|
||||
final float devX = devSumX / div;
|
||||
final float devY = devSumY / div;
|
||||
|
||||
// Span is the average distance between touch points through the focal point;
|
||||
// i.e. the diameter of the circle with a radius of the average deviation from
|
||||
// the focal point.
|
||||
final float spanX = devX * 2;
|
||||
final float spanY = devY * 2;
|
||||
final float span = FloatMath.sqrt(spanX * spanX + spanY * spanY);
|
||||
|
||||
// Dispatch begin/end events as needed.
|
||||
// If the configuration changes, notify the app to reset its current state by beginning
|
||||
// a fresh scale event stream.
|
||||
if (mInProgress && (span == 0 || configChanged)) {
|
||||
mListener.onScaleEnd(this);
|
||||
mInProgress = false;
|
||||
}
|
||||
if (configChanged) {
|
||||
mPrevSpanX = mCurrSpanX = spanX;
|
||||
mPrevSpanY = mCurrSpanY = spanY;
|
||||
mPrevSpan = mCurrSpan = span;
|
||||
}
|
||||
if (!mInProgress && span != 0) {
|
||||
mFocusX = focusX;
|
||||
mFocusY = focusY;
|
||||
mInProgress = mListener.onScaleBegin(this);
|
||||
}
|
||||
|
||||
// Handle motion; focal point and span/scale factor are changing.
|
||||
if (action == MotionEvent.ACTION_MOVE) {
|
||||
mCurrSpanX = spanX;
|
||||
mCurrSpanY = spanY;
|
||||
mCurrSpan = span;
|
||||
mFocusX = focusX;
|
||||
mFocusY = focusY;
|
||||
|
||||
boolean updatePrev = true;
|
||||
if (mInProgress) {
|
||||
updatePrev = mListener.onScale(this);
|
||||
}
|
||||
|
||||
if (updatePrev) {
|
||||
mPrevSpanX = mCurrSpanX;
|
||||
mPrevSpanY = mCurrSpanY;
|
||||
mPrevSpan = mCurrSpan;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if a two-finger scale gesture is in progress.
|
||||
* @return {@code true} if a scale gesture is in progress, {@code false} otherwise.
|
||||
* Returns {@code true} if a scale gesture is in progress.
|
||||
*/
|
||||
public boolean isInProgress() {
|
||||
return mGestureInProgress;
|
||||
return mInProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the X coordinate of the current gesture's focal point.
|
||||
* If a gesture is in progress, the focal point is directly between
|
||||
* the two pointers forming the gesture.
|
||||
* If a gesture is ending, the focal point is the location of the
|
||||
* remaining pointer on the screen.
|
||||
* If a gesture is in progress, the focal point is between
|
||||
* each of the pointers forming the gesture.
|
||||
*
|
||||
* If {@link #isInProgress()} would return false, the result of this
|
||||
* function is undefined.
|
||||
*
|
||||
@ -448,10 +282,9 @@ public class ScaleGestureDetector {
|
||||
|
||||
/**
|
||||
* Get the Y coordinate of the current gesture's focal point.
|
||||
* If a gesture is in progress, the focal point is directly between
|
||||
* the two pointers forming the gesture.
|
||||
* If a gesture is ending, the focal point is the location of the
|
||||
* remaining pointer on the screen.
|
||||
* If a gesture is in progress, the focal point is between
|
||||
* each of the pointers forming the gesture.
|
||||
*
|
||||
* If {@link #isInProgress()} would return false, the result of this
|
||||
* function is undefined.
|
||||
*
|
||||
@ -462,73 +295,63 @@ public class ScaleGestureDetector {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current distance between the two pointers forming the
|
||||
* gesture in progress.
|
||||
* Return the average distance between each of the pointers forming the
|
||||
* gesture in progress through the focal point.
|
||||
*
|
||||
* @return Distance between pointers in pixels.
|
||||
*/
|
||||
public float getCurrentSpan() {
|
||||
if (mCurrLen == -1) {
|
||||
final float cvx = mCurrFingerDiffX;
|
||||
final float cvy = mCurrFingerDiffY;
|
||||
mCurrLen = FloatMath.sqrt(cvx*cvx + cvy*cvy);
|
||||
}
|
||||
return mCurrLen;
|
||||
return mCurrSpan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current x distance between the two pointers forming the
|
||||
* gesture in progress.
|
||||
* Return the average X distance between each of the pointers forming the
|
||||
* gesture in progress through the focal point.
|
||||
*
|
||||
* @return Distance between pointers in pixels.
|
||||
*/
|
||||
public float getCurrentSpanX() {
|
||||
return mCurrFingerDiffX;
|
||||
return mCurrSpanX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current y distance between the two pointers forming the
|
||||
* gesture in progress.
|
||||
* Return the average Y distance between each of the pointers forming the
|
||||
* gesture in progress through the focal point.
|
||||
*
|
||||
* @return Distance between pointers in pixels.
|
||||
*/
|
||||
public float getCurrentSpanY() {
|
||||
return mCurrFingerDiffY;
|
||||
return mCurrSpanY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the previous distance between the two pointers forming the
|
||||
* gesture in progress.
|
||||
* Return the previous average distance between each of the pointers forming the
|
||||
* gesture in progress through the focal point.
|
||||
*
|
||||
* @return Previous distance between pointers in pixels.
|
||||
*/
|
||||
public float getPreviousSpan() {
|
||||
if (mPrevLen == -1) {
|
||||
final float pvx = mPrevFingerDiffX;
|
||||
final float pvy = mPrevFingerDiffY;
|
||||
mPrevLen = FloatMath.sqrt(pvx*pvx + pvy*pvy);
|
||||
}
|
||||
return mPrevLen;
|
||||
return mPrevSpan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the previous x distance between the two pointers forming the
|
||||
* gesture in progress.
|
||||
* Return the previous average X distance between each of the pointers forming the
|
||||
* gesture in progress through the focal point.
|
||||
*
|
||||
* @return Previous distance between pointers in pixels.
|
||||
*/
|
||||
public float getPreviousSpanX() {
|
||||
return mPrevFingerDiffX;
|
||||
return mPrevSpanX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the previous y distance between the two pointers forming the
|
||||
* gesture in progress.
|
||||
* Return the previous average Y distance between each of the pointers forming the
|
||||
* gesture in progress through the focal point.
|
||||
*
|
||||
* @return Previous distance between pointers in pixels.
|
||||
*/
|
||||
public float getPreviousSpanY() {
|
||||
return mPrevFingerDiffY;
|
||||
return mPrevSpanY;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -539,10 +362,7 @@ public class ScaleGestureDetector {
|
||||
* @return The current scaling factor.
|
||||
*/
|
||||
public float getScaleFactor() {
|
||||
if (mScaleFactor == -1) {
|
||||
mScaleFactor = getCurrentSpan() / getPreviousSpan();
|
||||
}
|
||||
return mScaleFactor;
|
||||
return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -552,7 +372,7 @@ public class ScaleGestureDetector {
|
||||
* @return Time difference since the last scaling event in milliseconds.
|
||||
*/
|
||||
public long getTimeDelta() {
|
||||
return mTimeDelta;
|
||||
return mCurrTime - mPrevTime;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -561,6 +381,6 @@ public class ScaleGestureDetector {
|
||||
* @return Current event time in milliseconds.
|
||||
*/
|
||||
public long getEventTime() {
|
||||
return mCurrEvent.getEventTime();
|
||||
return mCurrTime;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user