From e8ce8ba2b5633e479ccaa82d8e3147ccbba62961 Mon Sep 17 00:00:00 2001 From: Mindy Pereira Date: Wed, 21 Aug 2013 15:59:36 -0700 Subject: [PATCH] Add doubletap swipe to scalegesturedetector Change-Id: I070b09ed7295e0c3e07c6dffcdaba20226f7c2a3 --- api/current.txt | 1 + core/java/android/view/ScaleGestureDetector.java | 132 ++++++++++++++++++++--- 2 files changed, 120 insertions(+), 13 deletions(-) diff --git a/api/current.txt b/api/current.txt index 4b66d086b2cb..391d2780b5cc 100644 --- a/api/current.txt +++ b/api/current.txt @@ -27130,6 +27130,7 @@ package android.view { method public long getTimeDelta(); method public boolean isInProgress(); method public boolean onTouchEvent(android.view.MotionEvent); + method public void setQuickScaleEnabled(boolean); } public static abstract interface ScaleGestureDetector.OnScaleGestureListener { diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java index 51c5c7b52b6b..0bebc04f304f 100644 --- a/core/java/android/view/ScaleGestureDetector.java +++ b/core/java/android/view/ScaleGestureDetector.java @@ -18,6 +18,8 @@ package android.view; import android.content.Context; import android.content.res.Resources; +import android.os.Build; +import android.os.Handler; import android.os.SystemClock; import android.util.FloatMath; @@ -128,6 +130,8 @@ public class ScaleGestureDetector { private float mFocusX; private float mFocusY; + private boolean mDoubleTapScales; + private float mCurrSpan; private float mPrevSpan; private float mInitialSpan; @@ -148,9 +152,14 @@ public class ScaleGestureDetector { private int mTouchHistoryDirection; private long mTouchHistoryLastAcceptedTime; private int mTouchMinMajor; + private MotionEvent mDoubleTapEvent; + private int mDoubleTapMode = DOUBLE_TAP_MODE_NONE; + private final Handler mHandler; private static final long TOUCH_STABILIZE_TIME = 128; // ms - private static final int TOUCH_MIN_MAJOR = 48; // dp + private static final int DOUBLE_TAP_MODE_NONE = 0; + private static final int DOUBLE_TAP_MODE_IN_PROGRESS = 1; + /** * Consistency verifier for debugging purposes. @@ -158,8 +167,37 @@ public class ScaleGestureDetector { private final InputEventConsistencyVerifier mInputEventConsistencyVerifier = InputEventConsistencyVerifier.isInstrumentationEnabled() ? new InputEventConsistencyVerifier(this, 0) : null; + private GestureDetector mGestureDetector; + + private boolean mEventBeforeOrAboveStartingGestureEvent; + /** + * Creates a ScaleGestureDetector with the supplied listener. + * You may only use this constructor from a {@link android.os.Looper Looper} thread. + * + * @param context the application's context + * @param listener the listener invoked for all the callbacks, this must + * not be null. + * + * @throws NullPointerException if {@code listener} is null. + */ public ScaleGestureDetector(Context context, OnScaleGestureListener listener) { + this(context, listener, null); + } + + /** + * Creates a ScaleGestureDetector with the supplied listener. + * @see android.os.Handler#Handler() + * + * @param context the application's context + * @param listener the listener invoked for all the callbacks, this must + * not be null. + * @param handler the handler to use for running deferred listener events. + * + * @throws NullPointerException if {@code listener} is null. + */ + public ScaleGestureDetector(Context context, OnScaleGestureListener listener, + Handler handler) { mContext = context; mListener = listener; mSpanSlop = ViewConfiguration.get(context).getScaledTouchSlop() * 2; @@ -167,8 +205,12 @@ public class ScaleGestureDetector { final Resources res = context.getResources(); mTouchMinMajor = res.getDimensionPixelSize( com.android.internal.R.dimen.config_minScalingTouchMajor); - mMinSpan = res.getDimensionPixelSize( - com.android.internal.R.dimen.config_minScalingSpan); + mMinSpan = res.getDimensionPixelSize(com.android.internal.R.dimen.config_minScalingSpan); + mHandler = handler; + // Quick scale is enabled by default after JB_MR2 + if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN_MR2) { + setQuickScaleEnabled(true); + } } /** @@ -263,8 +305,14 @@ public class ScaleGestureDetector { final int action = event.getActionMasked(); + // Forward the event to check for double tap gesture + if (mDoubleTapScales) { + mGestureDetector.onTouchEvent(event); + } + 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. @@ -273,6 +321,7 @@ public class ScaleGestureDetector { mListener.onScaleEnd(this); mInProgress = false; mInitialSpan = 0; + mDoubleTapMode = DOUBLE_TAP_MODE_NONE; } if (streamComplete) { @@ -284,21 +333,37 @@ public class ScaleGestureDetector { final boolean configChanged = action == MotionEvent.ACTION_DOWN || 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; + final float focusX; + final float focusY; + if (mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS) { + // In double tap mode, the focal pt is always where the double tap + // gesture started + focusX = mDoubleTapEvent.getX(); + focusY = mDoubleTapEvent.getY(); + if (event.getY() < focusY) { + mEventBeforeOrAboveStartingGestureEvent = true; + } else { + mEventBeforeOrAboveStartingGestureEvent = false; + } + } else { + for (int i = 0; i < count; i++) { + if (skipIndex == i) continue; + sumX += event.getX(i); + sumY += event.getY(i); + } + focusX = sumX / div; + focusY = sumY / div; + } addTouchHistory(event); @@ -320,7 +385,12 @@ public class ScaleGestureDetector { // the focal point. final float spanX = devX * 2; final float spanY = devY * 2; - final float span = FloatMath.sqrt(spanX * spanX + spanY * spanY); + final float span; + if (inDoubleTapMode()) { + span = spanY; + } else { + 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 @@ -328,10 +398,11 @@ public class ScaleGestureDetector { final boolean wasInProgress = mInProgress; mFocusX = focusX; mFocusY = focusY; - if (mInProgress && (span < mMinSpan || configChanged)) { + if (!inDoubleTapMode() && mInProgress && (span < mMinSpan || configChanged)) { mListener.onScaleEnd(this); mInProgress = false; mInitialSpan = span; + mDoubleTapMode = DOUBLE_TAP_MODE_NONE; } if (configChanged) { mPrevSpanX = mCurrSpanX = spanX; @@ -354,6 +425,7 @@ public class ScaleGestureDetector { mCurrSpan = span; boolean updatePrev = true; + if (mInProgress) { updatePrev = mListener.onScale(this); } @@ -369,6 +441,34 @@ public class ScaleGestureDetector { return true; } + + private boolean inDoubleTapMode() { + return mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS; + } + + /** + * Set whether the associated {@link OnScaleGestureListener} should receive onScale callbacks + * when the user performs a doubleTap followed by a swipe. Note that this is enabled by default + * if the app targets API 19 and newer. + * @param scales true to enable quick scaling, false to disable + */ + public void setQuickScaleEnabled(boolean scales) { + mDoubleTapScales = scales; + if (mDoubleTapScales && mGestureDetector == null) { + GestureDetector.SimpleOnGestureListener gestureListener = + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onDoubleTap(MotionEvent e) { + // Double tap: start watching for a swipe + mDoubleTapEvent = e; + mDoubleTapMode = DOUBLE_TAP_MODE_IN_PROGRESS; + return true; + } + }; + mGestureDetector = new GestureDetector(mContext, gestureListener, mHandler); + } + } + /** * Returns {@code true} if a scale gesture is in progress. */ @@ -472,6 +572,12 @@ public class ScaleGestureDetector { * @return The current scaling factor. */ public float getScaleFactor() { + if (inDoubleTapMode() && mEventBeforeOrAboveStartingGestureEvent) { + // Drag is moving up; the further away from the gesture + // start, the smaller the span should be, the closer, + // the larger the span, and therefore the larger the scale + return (1 / mCurrSpan) / (1 / mPrevSpan); + } return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1; } @@ -493,4 +599,4 @@ public class ScaleGestureDetector { public long getEventTime() { return mCurrTime; } -} +} \ No newline at end of file -- 2.11.0