Loading core/java/android/view/ScaleGestureDetector.java +133 −313 Original line number Diff line number Diff line Loading @@ -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: Loading Loading @@ -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. Loading @@ -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); Loading @@ -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) { 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); mActiveId1 = event.getPointerId(newIndex); mActive0MostRecent = false; mPrevEvent = MotionEvent.obtain(event); setContext(event); mGestureInProgress = mListener.onScaleBegin(this); } else { gestureEnded = true; mInProgress = false; } } 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; if (streamComplete) { return true; } } break; case MotionEvent.ACTION_CANCEL: 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; // 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); } 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); } 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); 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); } mInProgress = false; } if (configChanged) { mPrevSpanX = mCurrSpanX = spanX; mPrevSpanY = mCurrSpanY = spanY; mPrevSpan = mCurrSpan = span; } break; } } if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } return handled; if (!mInProgress && span != 0) { mFocusX = focusX; mFocusY = focusY; mInProgress = mListener.onScaleBegin(this); } private int findNewActiveIndex(MotionEvent ev, int otherActiveId, int removedPointerIndex) { final int pointerCount = ev.getPointerCount(); // 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; // 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; boolean updatePrev = true; if (mInProgress) { updatePrev = mListener.onScale(this); } private void setContext(MotionEvent curr) { if (mCurrEvent != null) { mCurrEvent.recycle(); if (updatePrev) { mPrevSpanX = mCurrSpanX; mPrevSpanY = mCurrSpanY; mPrevSpan = mCurrSpan; } 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) { mListener.onScaleEnd(this); } 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 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; } if (mCurrEvent != null) { mCurrEvent.recycle(); mCurrEvent = null; } mGestureInProgress = false; mActiveId0 = -1; mActiveId1 = -1; mInvalidGesture = false; 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. * Loading @@ -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. * Loading @@ -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; } /** Loading @@ -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; } /** Loading @@ -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; } /** Loading @@ -561,6 +381,6 @@ public class ScaleGestureDetector { * @return Current event time in milliseconds. */ public long getEventTime() { return mCurrEvent.getEventTime(); return mCurrTime; } } Loading
core/java/android/view/ScaleGestureDetector.java +133 −313 Original line number Diff line number Diff line Loading @@ -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: Loading Loading @@ -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. Loading @@ -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); Loading @@ -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) { 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); mActiveId1 = event.getPointerId(newIndex); mActive0MostRecent = false; mPrevEvent = MotionEvent.obtain(event); setContext(event); mGestureInProgress = mListener.onScaleBegin(this); } else { gestureEnded = true; mInProgress = false; } } 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; if (streamComplete) { return true; } } break; case MotionEvent.ACTION_CANCEL: 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; // 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); } 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); } 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); 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); } mInProgress = false; } if (configChanged) { mPrevSpanX = mCurrSpanX = spanX; mPrevSpanY = mCurrSpanY = spanY; mPrevSpan = mCurrSpan = span; } break; } } if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } return handled; if (!mInProgress && span != 0) { mFocusX = focusX; mFocusY = focusY; mInProgress = mListener.onScaleBegin(this); } private int findNewActiveIndex(MotionEvent ev, int otherActiveId, int removedPointerIndex) { final int pointerCount = ev.getPointerCount(); // 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; // 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; boolean updatePrev = true; if (mInProgress) { updatePrev = mListener.onScale(this); } private void setContext(MotionEvent curr) { if (mCurrEvent != null) { mCurrEvent.recycle(); if (updatePrev) { mPrevSpanX = mCurrSpanX; mPrevSpanY = mCurrSpanY; mPrevSpan = mCurrSpan; } 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) { mListener.onScaleEnd(this); } 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 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; } if (mCurrEvent != null) { mCurrEvent.recycle(); mCurrEvent = null; } mGestureInProgress = false; mActiveId0 = -1; mActiveId1 = -1; mInvalidGesture = false; 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. * Loading @@ -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. * Loading @@ -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; } /** Loading @@ -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; } /** Loading @@ -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; } /** Loading @@ -561,6 +381,6 @@ public class ScaleGestureDetector { * @return Current event time in milliseconds. */ public long getEventTime() { return mCurrEvent.getEventTime(); return mCurrTime; } }