blob: c189afefe5f8f423d7fd03bd782270059037363a [file] [log] [blame]
Adam Powellae542ff2010-01-13 16:29:27 -08001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.view;
18
Mathew Inwoode5ad5982018-08-17 15:07:52 +010019import android.annotation.UnsupportedAppUsage;
Adam Powellae542ff2010-01-13 16:29:27 -080020import android.content.Context;
Adam Powell33079582012-10-09 11:20:39 -070021import android.content.res.Resources;
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -070022import android.os.Build;
23import android.os.Handler;
Adam Powella4ce6ae2012-09-24 20:13:23 -070024
Adam Powellae542ff2010-01-13 16:29:27 -080025/**
Adam Powell618cbea2012-08-27 17:44:59 -070026 * Detects scaling transformation gestures using the supplied {@link MotionEvent}s.
27 * The {@link OnScaleGestureListener} callback will notify users when a particular
28 * gesture event has occurred.
29 *
Adam Powellae542ff2010-01-13 16:29:27 -080030 * This class should only be used with {@link MotionEvent}s reported via touch.
Erik47c41e82010-09-01 15:39:08 -070031 *
Adam Powellae542ff2010-01-13 16:29:27 -080032 * To use this class:
33 * <ul>
34 * <li>Create an instance of the {@code ScaleGestureDetector} for your
35 * {@link View}
36 * <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
37 * {@link #onTouchEvent(MotionEvent)}. The methods defined in your
38 * callback will be executed when the events occur.
39 * </ul>
Adam Powellae542ff2010-01-13 16:29:27 -080040 */
41public class ScaleGestureDetector {
Adam Powell08180202011-03-07 16:48:29 -080042 private static final String TAG = "ScaleGestureDetector";
43
Adam Powellae542ff2010-01-13 16:29:27 -080044 /**
45 * The listener for receiving notifications when gestures occur.
46 * If you want to listen for all the different gestures then implement
47 * this interface. If you only want to listen for a subset it might
48 * be easier to extend {@link SimpleOnScaleGestureListener}.
Erik47c41e82010-09-01 15:39:08 -070049 *
Adam Powellae542ff2010-01-13 16:29:27 -080050 * An application will receive events in the following order:
51 * <ul>
Adam Powellab905c872010-02-03 11:01:58 -080052 * <li>One {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)}
53 * <li>Zero or more {@link OnScaleGestureListener#onScale(ScaleGestureDetector)}
54 * <li>One {@link OnScaleGestureListener#onScaleEnd(ScaleGestureDetector)}
Adam Powellae542ff2010-01-13 16:29:27 -080055 * </ul>
56 */
57 public interface OnScaleGestureListener {
58 /**
59 * Responds to scaling events for a gesture in progress.
60 * Reported by pointer motion.
Erik47c41e82010-09-01 15:39:08 -070061 *
Adam Powellae542ff2010-01-13 16:29:27 -080062 * @param detector The detector reporting the event - use this to
63 * retrieve extended info about event state.
64 * @return Whether or not the detector should consider this event
65 * as handled. If an event was not handled, the detector
66 * will continue to accumulate movement until an event is
67 * handled. This can be useful if an application, for example,
68 * only wants to update scaling factors if the change is
69 * greater than 0.01.
70 */
71 public boolean onScale(ScaleGestureDetector detector);
72
73 /**
74 * Responds to the beginning of a scaling gesture. Reported by
75 * new pointers going down.
Erik47c41e82010-09-01 15:39:08 -070076 *
Adam Powellae542ff2010-01-13 16:29:27 -080077 * @param detector The detector reporting the event - use this to
78 * retrieve extended info about event state.
79 * @return Whether or not the detector should continue recognizing
80 * this gesture. For example, if a gesture is beginning
81 * with a focal point outside of a region where it makes
82 * sense, onScaleBegin() may return false to ignore the
83 * rest of the gesture.
84 */
85 public boolean onScaleBegin(ScaleGestureDetector detector);
86
87 /**
88 * Responds to the end of a scale gesture. Reported by existing
Adam Powell216bccf2010-02-01 15:03:17 -080089 * pointers going up.
Erik47c41e82010-09-01 15:39:08 -070090 *
Adam Powellae542ff2010-01-13 16:29:27 -080091 * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()}
Adam Powell47ec2fb2012-08-31 17:15:32 -070092 * and {@link ScaleGestureDetector#getFocusY()} will return focal point
93 * of the pointers remaining on the screen.
Erik47c41e82010-09-01 15:39:08 -070094 *
Adam Powellae542ff2010-01-13 16:29:27 -080095 * @param detector The detector reporting the event - use this to
96 * retrieve extended info about event state.
97 */
98 public void onScaleEnd(ScaleGestureDetector detector);
99 }
Erik47c41e82010-09-01 15:39:08 -0700100
Adam Powellae542ff2010-01-13 16:29:27 -0800101 /**
102 * A convenience class to extend when you only want to listen for a subset
103 * of scaling-related events. This implements all methods in
104 * {@link OnScaleGestureListener} but does nothing.
Adam Powell346c8fb2010-03-12 10:52:35 -0800105 * {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} returns
106 * {@code false} so that a subclass can retrieve the accumulated scale
107 * factor in an overridden onScaleEnd.
108 * {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} returns
Erik47c41e82010-09-01 15:39:08 -0700109 * {@code true}.
Adam Powellae542ff2010-01-13 16:29:27 -0800110 */
Adam Powell216bccf2010-02-01 15:03:17 -0800111 public static class SimpleOnScaleGestureListener implements OnScaleGestureListener {
Adam Powellae542ff2010-01-13 16:29:27 -0800112
113 public boolean onScale(ScaleGestureDetector detector) {
Adam Powell346c8fb2010-03-12 10:52:35 -0800114 return false;
Adam Powellae542ff2010-01-13 16:29:27 -0800115 }
116
117 public boolean onScaleBegin(ScaleGestureDetector detector) {
118 return true;
119 }
120
121 public void onScaleEnd(ScaleGestureDetector detector) {
122 // Intentionally empty
123 }
124 }
125
Adam Powell346c8fb2010-03-12 10:52:35 -0800126 private final Context mContext;
Mathew Inwoode5ad5982018-08-17 15:07:52 +0100127 @UnsupportedAppUsage
Adam Powell346c8fb2010-03-12 10:52:35 -0800128 private final OnScaleGestureListener mListener;
Adam Powellae542ff2010-01-13 16:29:27 -0800129
130 private float mFocusX;
131 private float mFocusY;
Erik47c41e82010-09-01 15:39:08 -0700132
Mindy Pereira9f1221f2013-09-16 11:36:12 -0700133 private boolean mQuickScaleEnabled;
Mady Mellor68955182015-04-16 12:50:53 -0700134 private boolean mStylusScaleEnabled;
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700135
Adam Powell618cbea2012-08-27 17:44:59 -0700136 private float mCurrSpan;
137 private float mPrevSpan;
Adam Powell47ec2fb2012-08-31 17:15:32 -0700138 private float mInitialSpan;
Adam Powell618cbea2012-08-27 17:44:59 -0700139 private float mCurrSpanX;
140 private float mCurrSpanY;
141 private float mPrevSpanX;
142 private float mPrevSpanY;
143 private long mCurrTime;
144 private long mPrevTime;
145 private boolean mInProgress;
Mathew Inwoode5ad5982018-08-17 15:07:52 +0100146 @UnsupportedAppUsage
Adam Powell47ec2fb2012-08-31 17:15:32 -0700147 private int mSpanSlop;
Mathew Inwoode5ad5982018-08-17 15:07:52 +0100148 @UnsupportedAppUsage
Adam Powell828e56e2012-09-14 18:56:16 -0700149 private int mMinSpan;
Adam Powelle33cef82011-02-23 16:51:20 -0800150
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700151 private final Handler mHandler;
Adam Powella4ce6ae2012-09-24 20:13:23 -0700152
Mady Mellor847d17f2015-03-30 09:42:12 -0700153 private float mAnchoredScaleStartX;
154 private float mAnchoredScaleStartY;
155 private int mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
156
Adam Powelld736d202012-09-25 19:30:44 -0700157 private static final long TOUCH_STABILIZE_TIME = 128; // ms
Mindy Pereira24870ce2013-09-09 15:56:23 -0700158 private static final float SCALE_FACTOR = .5f;
Mady Mellor847d17f2015-03-30 09:42:12 -0700159 private static final int ANCHORED_SCALE_MODE_NONE = 0;
160 private static final int ANCHORED_SCALE_MODE_DOUBLE_TAP = 1;
Mady Mellor68955182015-04-16 12:50:53 -0700161 private static final int ANCHORED_SCALE_MODE_STYLUS = 2;
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700162
Adam Powella4ce6ae2012-09-24 20:13:23 -0700163
Jeff Brown21bc5c92011-02-28 18:27:14 -0800164 /**
165 * Consistency verifier for debugging purposes.
166 */
167 private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
168 InputEventConsistencyVerifier.isInstrumentationEnabled() ?
169 new InputEventConsistencyVerifier(this, 0) : null;
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700170 private GestureDetector mGestureDetector;
Jeff Brown21bc5c92011-02-28 18:27:14 -0800171
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700172 private boolean mEventBeforeOrAboveStartingGestureEvent;
173
174 /**
175 * Creates a ScaleGestureDetector with the supplied listener.
176 * You may only use this constructor from a {@link android.os.Looper Looper} thread.
177 *
178 * @param context the application's context
179 * @param listener the listener invoked for all the callbacks, this must
180 * not be null.
181 *
182 * @throws NullPointerException if {@code listener} is null.
183 */
Adam Powellae542ff2010-01-13 16:29:27 -0800184 public ScaleGestureDetector(Context context, OnScaleGestureListener listener) {
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700185 this(context, listener, null);
186 }
187
188 /**
189 * Creates a ScaleGestureDetector with the supplied listener.
190 * @see android.os.Handler#Handler()
191 *
192 * @param context the application's context
193 * @param listener the listener invoked for all the callbacks, this must
194 * not be null.
195 * @param handler the handler to use for running deferred listener events.
196 *
197 * @throws NullPointerException if {@code listener} is null.
198 */
199 public ScaleGestureDetector(Context context, OnScaleGestureListener listener,
Mindy Pereira24870ce2013-09-09 15:56:23 -0700200 Handler handler) {
Adam Powellae542ff2010-01-13 16:29:27 -0800201 mContext = context;
202 mListener = listener;
Adam Powell47ec2fb2012-08-31 17:15:32 -0700203 mSpanSlop = ViewConfiguration.get(context).getScaledTouchSlop() * 2;
Adam Powell33079582012-10-09 11:20:39 -0700204
205 final Resources res = context.getResources();
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700206 mMinSpan = res.getDimensionPixelSize(com.android.internal.R.dimen.config_minScalingSpan);
207 mHandler = handler;
208 // Quick scale is enabled by default after JB_MR2
Mady Mellor7c36a682015-04-22 11:52:31 -0700209 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
210 if (targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN_MR2) {
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700211 setQuickScaleEnabled(true);
212 }
Mady Mellor7c36a682015-04-22 11:52:31 -0700213 // Stylus scale is enabled by default after LOLLIPOP_MR1
214 if (targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
215 setStylusScaleEnabled(true);
216 }
Adam Powellae542ff2010-01-13 16:29:27 -0800217 }
218
Adam Powell618cbea2012-08-27 17:44:59 -0700219 /**
220 * Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener}
221 * when appropriate.
222 *
223 * <p>Applications should pass a complete and consistent event stream to this method.
224 * A complete and consistent event stream involves all MotionEvents from the initial
225 * ACTION_DOWN to the final ACTION_UP or ACTION_CANCEL.</p>
226 *
227 * @param event The event to process
228 * @return true if the event was processed and the detector wants to receive the
229 * rest of the MotionEvents in this event stream.
230 */
Adam Powellae542ff2010-01-13 16:29:27 -0800231 public boolean onTouchEvent(MotionEvent event) {
Jeff Brown21bc5c92011-02-28 18:27:14 -0800232 if (mInputEventConsistencyVerifier != null) {
233 mInputEventConsistencyVerifier.onTouchEvent(event, 0);
234 }
235
Adam Powell7232b0a2012-11-28 18:29:22 -0800236 mCurrTime = event.getEventTime();
237
Adam Powelle33cef82011-02-23 16:51:20 -0800238 final int action = event.getActionMasked();
Adam Powellae542ff2010-01-13 16:29:27 -0800239
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700240 // Forward the event to check for double tap gesture
Mindy Pereira9f1221f2013-09-16 11:36:12 -0700241 if (mQuickScaleEnabled) {
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700242 mGestureDetector.onTouchEvent(event);
243 }
244
Mady Mellor847d17f2015-03-30 09:42:12 -0700245 final int count = event.getPointerCount();
Mady Mellor772fcb92015-05-21 17:46:58 -0700246 final boolean isStylusButtonDown =
247 (event.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;
Mady Mellor847d17f2015-03-30 09:42:12 -0700248
249 final boolean anchoredScaleCancelled =
Mady Mellor68955182015-04-16 12:50:53 -0700250 mAnchoredScaleMode == ANCHORED_SCALE_MODE_STYLUS && !isStylusButtonDown;
Adam Powell618cbea2012-08-27 17:44:59 -0700251 final boolean streamComplete = action == MotionEvent.ACTION_UP ||
Mady Mellor847d17f2015-03-30 09:42:12 -0700252 action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700253
Adam Powell618cbea2012-08-27 17:44:59 -0700254 if (action == MotionEvent.ACTION_DOWN || streamComplete) {
255 // Reset any scale in progress with the listener.
256 // If it's an ACTION_DOWN we're beginning a new event stream.
257 // This means the app probably didn't give us all the events. Shame on it.
258 if (mInProgress) {
Adam Powelld0197f32011-03-17 00:09:03 -0700259 mListener.onScaleEnd(this);
Adam Powell618cbea2012-08-27 17:44:59 -0700260 mInProgress = false;
Adam Powell47ec2fb2012-08-31 17:15:32 -0700261 mInitialSpan = 0;
Mady Mellor847d17f2015-03-30 09:42:12 -0700262 mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
263 } else if (inAnchoredScaleMode() && streamComplete) {
Mindy Pereira58233522013-11-14 17:01:25 -0800264 mInProgress = false;
265 mInitialSpan = 0;
Mady Mellor847d17f2015-03-30 09:42:12 -0700266 mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
Adam Powelld0197f32011-03-17 00:09:03 -0700267 }
Adam Powell618cbea2012-08-27 17:44:59 -0700268
269 if (streamComplete) {
270 return true;
271 }
Adam Powelld0197f32011-03-17 00:09:03 -0700272 }
273
Mady Mellor68955182015-04-16 12:50:53 -0700274 if (!mInProgress && mStylusScaleEnabled && !inAnchoredScaleMode()
275 && !streamComplete && isStylusButtonDown) {
Mady Mellor847d17f2015-03-30 09:42:12 -0700276 // Start of a button scale gesture
277 mAnchoredScaleStartX = event.getX();
278 mAnchoredScaleStartY = event.getY();
Mady Mellor68955182015-04-16 12:50:53 -0700279 mAnchoredScaleMode = ANCHORED_SCALE_MODE_STYLUS;
Mady Mellor847d17f2015-03-30 09:42:12 -0700280 mInitialSpan = 0;
281 }
282
Adam Powellabde0422012-09-26 17:12:50 -0700283 final boolean configChanged = action == MotionEvent.ACTION_DOWN ||
Adam Powell618cbea2012-08-27 17:44:59 -0700284 action == MotionEvent.ACTION_POINTER_UP ||
Mady Mellor847d17f2015-03-30 09:42:12 -0700285 action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled;
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700286
Adam Powell618cbea2012-08-27 17:44:59 -0700287 final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
288 final int skipIndex = pointerUp ? event.getActionIndex() : -1;
Adam Powellae542ff2010-01-13 16:29:27 -0800289
Adam Powell618cbea2012-08-27 17:44:59 -0700290 // Determine focal point
291 float sumX = 0, sumY = 0;
Adam Powell618cbea2012-08-27 17:44:59 -0700292 final int div = pointerUp ? count - 1 : count;
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700293 final float focusX;
294 final float focusY;
Mady Mellor847d17f2015-03-30 09:42:12 -0700295 if (inAnchoredScaleMode()) {
296 // In anchored scale mode, the focal pt is always where the double tap
297 // or button down gesture started
298 focusX = mAnchoredScaleStartX;
299 focusY = mAnchoredScaleStartY;
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700300 if (event.getY() < focusY) {
301 mEventBeforeOrAboveStartingGestureEvent = true;
302 } else {
303 mEventBeforeOrAboveStartingGestureEvent = false;
304 }
305 } else {
306 for (int i = 0; i < count; i++) {
307 if (skipIndex == i) continue;
308 sumX += event.getX(i);
309 sumY += event.getY(i);
310 }
Adam Powell618cbea2012-08-27 17:44:59 -0700311
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700312 focusX = sumX / div;
313 focusY = sumY / div;
314 }
Adam Powell5b5c4142012-10-02 16:25:30 -0700315
Adam Powell618cbea2012-08-27 17:44:59 -0700316 // Determine average deviation from focal point
317 float devSumX = 0, devSumY = 0;
318 for (int i = 0; i < count; i++) {
319 if (skipIndex == i) continue;
Adam Powell828e56e2012-09-14 18:56:16 -0700320
Adam Powell5b5c4142012-10-02 16:25:30 -0700321 // Convert the resulting diameter into a radius.
Adam Powellc6df18f2016-04-04 14:42:24 -0700322 devSumX += Math.abs(event.getX(i) - focusX);
323 devSumY += Math.abs(event.getY(i) - focusY);
Adam Powellae542ff2010-01-13 16:29:27 -0800324 }
Adam Powell618cbea2012-08-27 17:44:59 -0700325 final float devX = devSumX / div;
326 final float devY = devSumY / div;
327
328 // Span is the average distance between touch points through the focal point;
329 // i.e. the diameter of the circle with a radius of the average deviation from
330 // the focal point.
331 final float spanX = devX * 2;
332 final float spanY = devY * 2;
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700333 final float span;
Mady Mellor847d17f2015-03-30 09:42:12 -0700334 if (inAnchoredScaleMode()) {
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700335 span = spanY;
336 } else {
Neil Fuller33253a42014-10-01 11:55:10 +0100337 span = (float) Math.hypot(spanX, spanY);
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700338 }
Adam Powell618cbea2012-08-27 17:44:59 -0700339
340 // Dispatch begin/end events as needed.
341 // If the configuration changes, notify the app to reset its current state by beginning
342 // a fresh scale event stream.
Adam Powell47ec2fb2012-08-31 17:15:32 -0700343 final boolean wasInProgress = mInProgress;
344 mFocusX = focusX;
345 mFocusY = focusY;
Mady Mellor847d17f2015-03-30 09:42:12 -0700346 if (!inAnchoredScaleMode() && mInProgress && (span < mMinSpan || configChanged)) {
Adam Powell618cbea2012-08-27 17:44:59 -0700347 mListener.onScaleEnd(this);
348 mInProgress = false;
Adam Powell47ec2fb2012-08-31 17:15:32 -0700349 mInitialSpan = span;
Adam Powell618cbea2012-08-27 17:44:59 -0700350 }
351 if (configChanged) {
352 mPrevSpanX = mCurrSpanX = spanX;
353 mPrevSpanY = mCurrSpanY = spanY;
Adam Powell47ec2fb2012-08-31 17:15:32 -0700354 mInitialSpan = mPrevSpan = mCurrSpan = span;
Adam Powell618cbea2012-08-27 17:44:59 -0700355 }
Mindy Pereira24870ce2013-09-09 15:56:23 -0700356
Mady Mellor847d17f2015-03-30 09:42:12 -0700357 final int minSpan = inAnchoredScaleMode() ? mSpanSlop : mMinSpan;
Mindy Pereira24870ce2013-09-09 15:56:23 -0700358 if (!mInProgress && span >= minSpan &&
Adam Powell47ec2fb2012-08-31 17:15:32 -0700359 (wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {
360 mPrevSpanX = mCurrSpanX = spanX;
361 mPrevSpanY = mCurrSpanY = spanY;
362 mPrevSpan = mCurrSpan = span;
Adam Powell7232b0a2012-11-28 18:29:22 -0800363 mPrevTime = mCurrTime;
Adam Powell618cbea2012-08-27 17:44:59 -0700364 mInProgress = mListener.onScaleBegin(this);
365 }
366
367 // Handle motion; focal point and span/scale factor are changing.
368 if (action == MotionEvent.ACTION_MOVE) {
369 mCurrSpanX = spanX;
370 mCurrSpanY = spanY;
371 mCurrSpan = span;
Adam Powell618cbea2012-08-27 17:44:59 -0700372
373 boolean updatePrev = true;
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700374
Adam Powell618cbea2012-08-27 17:44:59 -0700375 if (mInProgress) {
376 updatePrev = mListener.onScale(this);
377 }
378
379 if (updatePrev) {
380 mPrevSpanX = mCurrSpanX;
381 mPrevSpanY = mCurrSpanY;
382 mPrevSpan = mCurrSpan;
Adam Powell7232b0a2012-11-28 18:29:22 -0800383 mPrevTime = mCurrTime;
Adam Powell618cbea2012-08-27 17:44:59 -0700384 }
385 }
386
387 return true;
Adam Powellae542ff2010-01-13 16:29:27 -0800388 }
389
Mady Mellor847d17f2015-03-30 09:42:12 -0700390 private boolean inAnchoredScaleMode() {
391 return mAnchoredScaleMode != ANCHORED_SCALE_MODE_NONE;
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700392 }
393
394 /**
395 * Set whether the associated {@link OnScaleGestureListener} should receive onScale callbacks
396 * when the user performs a doubleTap followed by a swipe. Note that this is enabled by default
397 * if the app targets API 19 and newer.
398 * @param scales true to enable quick scaling, false to disable
399 */
400 public void setQuickScaleEnabled(boolean scales) {
Mindy Pereira9f1221f2013-09-16 11:36:12 -0700401 mQuickScaleEnabled = scales;
402 if (mQuickScaleEnabled && mGestureDetector == null) {
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700403 GestureDetector.SimpleOnGestureListener gestureListener =
404 new GestureDetector.SimpleOnGestureListener() {
405 @Override
406 public boolean onDoubleTap(MotionEvent e) {
407 // Double tap: start watching for a swipe
Mady Mellor847d17f2015-03-30 09:42:12 -0700408 mAnchoredScaleStartX = e.getX();
409 mAnchoredScaleStartY = e.getY();
410 mAnchoredScaleMode = ANCHORED_SCALE_MODE_DOUBLE_TAP;
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700411 return true;
412 }
Mindy Pereira24870ce2013-09-09 15:56:23 -0700413 };
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700414 mGestureDetector = new GestureDetector(mContext, gestureListener, mHandler);
415 }
416 }
417
Mindy Pereira9f1221f2013-09-16 11:36:12 -0700418 /**
419 * Return whether the quick scale gesture, in which the user performs a double tap followed by a
420 * swipe, should perform scaling. {@see #setQuickScaleEnabled(boolean)}.
421 */
422 public boolean isQuickScaleEnabled() {
423 return mQuickScaleEnabled;
424 }
425
Adam Powellae542ff2010-01-13 16:29:27 -0800426 /**
Mady Mellor68955182015-04-16 12:50:53 -0700427 * Sets whether the associates {@link OnScaleGestureListener} should receive
428 * onScale callbacks when the user uses a stylus and presses the button.
429 * Note that this is enabled by default if the app targets API 23 and newer.
Mady Mellor847d17f2015-03-30 09:42:12 -0700430 *
Mady Mellor68955182015-04-16 12:50:53 -0700431 * @param scales true to enable stylus scaling, false to disable.
Mady Mellor847d17f2015-03-30 09:42:12 -0700432 */
Mady Mellor68955182015-04-16 12:50:53 -0700433 public void setStylusScaleEnabled(boolean scales) {
434 mStylusScaleEnabled = scales;
Mady Mellor847d17f2015-03-30 09:42:12 -0700435 }
436
437 /**
Mady Mellord9ff4df2015-06-04 18:03:49 -0700438 * Return whether the stylus scale gesture, in which the user uses a stylus and presses the
439 * button, should perform scaling. {@see #setStylusScaleEnabled(boolean)}
Mady Mellor847d17f2015-03-30 09:42:12 -0700440 */
Mady Mellor68955182015-04-16 12:50:53 -0700441 public boolean isStylusScaleEnabled() {
442 return mStylusScaleEnabled;
Mady Mellor847d17f2015-03-30 09:42:12 -0700443 }
444
445 /**
Adam Powell618cbea2012-08-27 17:44:59 -0700446 * Returns {@code true} if a scale gesture is in progress.
Adam Powellae542ff2010-01-13 16:29:27 -0800447 */
448 public boolean isInProgress() {
Adam Powell618cbea2012-08-27 17:44:59 -0700449 return mInProgress;
Adam Powellae542ff2010-01-13 16:29:27 -0800450 }
451
452 /**
453 * Get the X coordinate of the current gesture's focal point.
Adam Powell618cbea2012-08-27 17:44:59 -0700454 * If a gesture is in progress, the focal point is between
455 * each of the pointers forming the gesture.
456 *
Adam Powellab905c872010-02-03 11:01:58 -0800457 * If {@link #isInProgress()} would return false, the result of this
Adam Powellae542ff2010-01-13 16:29:27 -0800458 * function is undefined.
Erik47c41e82010-09-01 15:39:08 -0700459 *
Adam Powellae542ff2010-01-13 16:29:27 -0800460 * @return X coordinate of the focal point in pixels.
461 */
462 public float getFocusX() {
463 return mFocusX;
464 }
465
466 /**
467 * Get the Y coordinate of the current gesture's focal point.
Adam Powell618cbea2012-08-27 17:44:59 -0700468 * If a gesture is in progress, the focal point is between
469 * each of the pointers forming the gesture.
470 *
Adam Powellab905c872010-02-03 11:01:58 -0800471 * If {@link #isInProgress()} would return false, the result of this
Adam Powellae542ff2010-01-13 16:29:27 -0800472 * function is undefined.
Erik47c41e82010-09-01 15:39:08 -0700473 *
Adam Powellae542ff2010-01-13 16:29:27 -0800474 * @return Y coordinate of the focal point in pixels.
475 */
476 public float getFocusY() {
477 return mFocusY;
478 }
479
480 /**
Adam Powell618cbea2012-08-27 17:44:59 -0700481 * Return the average distance between each of the pointers forming the
482 * gesture in progress through the focal point.
Erik47c41e82010-09-01 15:39:08 -0700483 *
Adam Powellae542ff2010-01-13 16:29:27 -0800484 * @return Distance between pointers in pixels.
485 */
486 public float getCurrentSpan() {
Adam Powell618cbea2012-08-27 17:44:59 -0700487 return mCurrSpan;
Adam Powellae542ff2010-01-13 16:29:27 -0800488 }
489
490 /**
Adam Powell618cbea2012-08-27 17:44:59 -0700491 * Return the average X distance between each of the pointers forming the
492 * gesture in progress through the focal point.
Erik47c41e82010-09-01 15:39:08 -0700493 *
494 * @return Distance between pointers in pixels.
495 */
496 public float getCurrentSpanX() {
Adam Powell618cbea2012-08-27 17:44:59 -0700497 return mCurrSpanX;
Erik47c41e82010-09-01 15:39:08 -0700498 }
499
500 /**
Adam Powell618cbea2012-08-27 17:44:59 -0700501 * Return the average Y distance between each of the pointers forming the
502 * gesture in progress through the focal point.
Erik47c41e82010-09-01 15:39:08 -0700503 *
504 * @return Distance between pointers in pixels.
505 */
506 public float getCurrentSpanY() {
Adam Powell618cbea2012-08-27 17:44:59 -0700507 return mCurrSpanY;
Erik47c41e82010-09-01 15:39:08 -0700508 }
509
510 /**
Adam Powell618cbea2012-08-27 17:44:59 -0700511 * Return the previous average distance between each of the pointers forming the
512 * gesture in progress through the focal point.
Erik47c41e82010-09-01 15:39:08 -0700513 *
Adam Powellae542ff2010-01-13 16:29:27 -0800514 * @return Previous distance between pointers in pixels.
515 */
516 public float getPreviousSpan() {
Adam Powell618cbea2012-08-27 17:44:59 -0700517 return mPrevSpan;
Adam Powellae542ff2010-01-13 16:29:27 -0800518 }
519
520 /**
Adam Powell618cbea2012-08-27 17:44:59 -0700521 * Return the previous average X distance between each of the pointers forming the
522 * gesture in progress through the focal point.
Erik47c41e82010-09-01 15:39:08 -0700523 *
524 * @return Previous distance between pointers in pixels.
525 */
526 public float getPreviousSpanX() {
Adam Powell618cbea2012-08-27 17:44:59 -0700527 return mPrevSpanX;
Erik47c41e82010-09-01 15:39:08 -0700528 }
529
530 /**
Adam Powell618cbea2012-08-27 17:44:59 -0700531 * Return the previous average Y distance between each of the pointers forming the
532 * gesture in progress through the focal point.
Erik47c41e82010-09-01 15:39:08 -0700533 *
534 * @return Previous distance between pointers in pixels.
535 */
536 public float getPreviousSpanY() {
Adam Powell618cbea2012-08-27 17:44:59 -0700537 return mPrevSpanY;
Erik47c41e82010-09-01 15:39:08 -0700538 }
539
540 /**
Adam Powellae542ff2010-01-13 16:29:27 -0800541 * Return the scaling factor from the previous scale event to the current
542 * event. This value is defined as
Adam Powellab905c872010-02-03 11:01:58 -0800543 * ({@link #getCurrentSpan()} / {@link #getPreviousSpan()}).
Erik47c41e82010-09-01 15:39:08 -0700544 *
Adam Powellae542ff2010-01-13 16:29:27 -0800545 * @return The current scaling factor.
546 */
547 public float getScaleFactor() {
Mady Mellor847d17f2015-03-30 09:42:12 -0700548 if (inAnchoredScaleMode()) {
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700549 // Drag is moving up; the further away from the gesture
550 // start, the smaller the span should be, the closer,
551 // the larger the span, and therefore the larger the scale
Mindy Pereira24870ce2013-09-09 15:56:23 -0700552 final boolean scaleUp =
553 (mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan < mPrevSpan)) ||
554 (!mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan > mPrevSpan));
555 final float spanDiff = (Math.abs(1 - (mCurrSpan / mPrevSpan)) * SCALE_FACTOR);
556 return mPrevSpan <= 0 ? 1 : scaleUp ? (1 + spanDiff) : (1 - spanDiff);
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700557 }
Adam Powell618cbea2012-08-27 17:44:59 -0700558 return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
Adam Powellae542ff2010-01-13 16:29:27 -0800559 }
Erik47c41e82010-09-01 15:39:08 -0700560
Adam Powellae542ff2010-01-13 16:29:27 -0800561 /**
562 * Return the time difference in milliseconds between the previous
563 * accepted scaling event and the current scaling event.
Erik47c41e82010-09-01 15:39:08 -0700564 *
Adam Powellae542ff2010-01-13 16:29:27 -0800565 * @return Time difference since the last scaling event in milliseconds.
566 */
567 public long getTimeDelta() {
Adam Powell618cbea2012-08-27 17:44:59 -0700568 return mCurrTime - mPrevTime;
Adam Powellae542ff2010-01-13 16:29:27 -0800569 }
Erik47c41e82010-09-01 15:39:08 -0700570
Adam Powellae542ff2010-01-13 16:29:27 -0800571 /**
572 * Return the event time of the current event being processed.
Erik47c41e82010-09-01 15:39:08 -0700573 *
Adam Powellae542ff2010-01-13 16:29:27 -0800574 * @return Current event time in milliseconds.
575 */
576 public long getEventTime() {
Adam Powell618cbea2012-08-27 17:44:59 -0700577 return mCurrTime;
Adam Powellae542ff2010-01-13 16:29:27 -0800578 }
Mindy Pereirae8ce8ba2013-08-21 15:59:36 -0700579}