OSDN Git Service

am fa2dbd69: Merge "Doc change: GCM category & dry_run params" into jb-dev-docs
[android-x86/frameworks-base.git] / core / java / android / view / ScaleGestureDetector.java
1 /*
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
17 package android.view;
18
19 import android.content.Context;
20 import android.util.FloatMath;
21
22 /**
23  * Detects scaling transformation gestures using the supplied {@link MotionEvent}s.
24  * The {@link OnScaleGestureListener} callback will notify users when a particular
25  * gesture event has occurred.
26  *
27  * This class should only be used with {@link MotionEvent}s reported via touch.
28  *
29  * To use this class:
30  * <ul>
31  *  <li>Create an instance of the {@code ScaleGestureDetector} for your
32  *      {@link View}
33  *  <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
34  *          {@link #onTouchEvent(MotionEvent)}. The methods defined in your
35  *          callback will be executed when the events occur.
36  * </ul>
37  */
38 public class ScaleGestureDetector {
39     private static final String TAG = "ScaleGestureDetector";
40
41     /**
42      * The listener for receiving notifications when gestures occur.
43      * If you want to listen for all the different gestures then implement
44      * this interface. If you only want to listen for a subset it might
45      * be easier to extend {@link SimpleOnScaleGestureListener}.
46      *
47      * An application will receive events in the following order:
48      * <ul>
49      *  <li>One {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)}
50      *  <li>Zero or more {@link OnScaleGestureListener#onScale(ScaleGestureDetector)}
51      *  <li>One {@link OnScaleGestureListener#onScaleEnd(ScaleGestureDetector)}
52      * </ul>
53      */
54     public interface OnScaleGestureListener {
55         /**
56          * Responds to scaling events for a gesture in progress.
57          * Reported by pointer motion.
58          *
59          * @param detector The detector reporting the event - use this to
60          *          retrieve extended info about event state.
61          * @return Whether or not the detector should consider this event
62          *          as handled. If an event was not handled, the detector
63          *          will continue to accumulate movement until an event is
64          *          handled. This can be useful if an application, for example,
65          *          only wants to update scaling factors if the change is
66          *          greater than 0.01.
67          */
68         public boolean onScale(ScaleGestureDetector detector);
69
70         /**
71          * Responds to the beginning of a scaling gesture. Reported by
72          * new pointers going down.
73          *
74          * @param detector The detector reporting the event - use this to
75          *          retrieve extended info about event state.
76          * @return Whether or not the detector should continue recognizing
77          *          this gesture. For example, if a gesture is beginning
78          *          with a focal point outside of a region where it makes
79          *          sense, onScaleBegin() may return false to ignore the
80          *          rest of the gesture.
81          */
82         public boolean onScaleBegin(ScaleGestureDetector detector);
83
84         /**
85          * Responds to the end of a scale gesture. Reported by existing
86          * pointers going up.
87          *
88          * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()}
89          * and {@link ScaleGestureDetector#getFocusY()} will return focal point
90          * of the pointers remaining on the screen.
91          *
92          * @param detector The detector reporting the event - use this to
93          *          retrieve extended info about event state.
94          */
95         public void onScaleEnd(ScaleGestureDetector detector);
96     }
97
98     /**
99      * A convenience class to extend when you only want to listen for a subset
100      * of scaling-related events. This implements all methods in
101      * {@link OnScaleGestureListener} but does nothing.
102      * {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} returns
103      * {@code false} so that a subclass can retrieve the accumulated scale
104      * factor in an overridden onScaleEnd.
105      * {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} returns
106      * {@code true}.
107      */
108     public static class SimpleOnScaleGestureListener implements OnScaleGestureListener {
109
110         public boolean onScale(ScaleGestureDetector detector) {
111             return false;
112         }
113
114         public boolean onScaleBegin(ScaleGestureDetector detector) {
115             return true;
116         }
117
118         public void onScaleEnd(ScaleGestureDetector detector) {
119             // Intentionally empty
120         }
121     }
122
123     private final Context mContext;
124     private final OnScaleGestureListener mListener;
125
126     private float mFocusX;
127     private float mFocusY;
128
129     private float mCurrSpan;
130     private float mPrevSpan;
131     private float mInitialSpan;
132     private float mCurrSpanX;
133     private float mCurrSpanY;
134     private float mPrevSpanX;
135     private float mPrevSpanY;
136     private long mCurrTime;
137     private long mPrevTime;
138     private boolean mInProgress;
139     private int mSpanSlop;
140
141     /**
142      * Consistency verifier for debugging purposes.
143      */
144     private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
145             InputEventConsistencyVerifier.isInstrumentationEnabled() ?
146                     new InputEventConsistencyVerifier(this, 0) : null;
147
148     public ScaleGestureDetector(Context context, OnScaleGestureListener listener) {
149         mContext = context;
150         mListener = listener;
151         mSpanSlop = ViewConfiguration.get(context).getScaledTouchSlop() * 2;
152     }
153
154     /**
155      * Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener}
156      * when appropriate.
157      *
158      * <p>Applications should pass a complete and consistent event stream to this method.
159      * A complete and consistent event stream involves all MotionEvents from the initial
160      * ACTION_DOWN to the final ACTION_UP or ACTION_CANCEL.</p>
161      *
162      * @param event The event to process
163      * @return true if the event was processed and the detector wants to receive the
164      *         rest of the MotionEvents in this event stream.
165      */
166     public boolean onTouchEvent(MotionEvent event) {
167         if (mInputEventConsistencyVerifier != null) {
168             mInputEventConsistencyVerifier.onTouchEvent(event, 0);
169         }
170
171         final int action = event.getActionMasked();
172
173         final boolean streamComplete = action == MotionEvent.ACTION_UP ||
174                 action == MotionEvent.ACTION_CANCEL;
175         if (action == MotionEvent.ACTION_DOWN || streamComplete) {
176             // Reset any scale in progress with the listener.
177             // If it's an ACTION_DOWN we're beginning a new event stream.
178             // This means the app probably didn't give us all the events. Shame on it.
179             if (mInProgress) {
180                 mListener.onScaleEnd(this);
181                 mInProgress = false;
182                 mInitialSpan = 0;
183             }
184
185             if (streamComplete) {
186                 return true;
187             }
188         }
189
190         final boolean configChanged =
191                 action == MotionEvent.ACTION_POINTER_UP ||
192                 action == MotionEvent.ACTION_POINTER_DOWN;
193         final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
194         final int skipIndex = pointerUp ? event.getActionIndex() : -1;
195
196         // Determine focal point
197         float sumX = 0, sumY = 0;
198         final int count = event.getPointerCount();
199         for (int i = 0; i < count; i++) {
200             if (skipIndex == i) continue;
201             sumX += event.getX(i);
202             sumY += event.getY(i);
203         }
204         final int div = pointerUp ? count - 1 : count;
205         final float focusX = sumX / div;
206         final float focusY = sumY / div;
207
208         // Determine average deviation from focal point
209         float devSumX = 0, devSumY = 0;
210         for (int i = 0; i < count; i++) {
211             if (skipIndex == i) continue;
212             devSumX += Math.abs(event.getX(i) - focusX);
213             devSumY += Math.abs(event.getY(i) - focusY);
214         }
215         final float devX = devSumX / div;
216         final float devY = devSumY / div;
217
218         // Span is the average distance between touch points through the focal point;
219         // i.e. the diameter of the circle with a radius of the average deviation from
220         // the focal point.
221         final float spanX = devX * 2;
222         final float spanY = devY * 2;
223         final float span = FloatMath.sqrt(spanX * spanX + spanY * spanY);
224
225         // Dispatch begin/end events as needed.
226         // If the configuration changes, notify the app to reset its current state by beginning
227         // a fresh scale event stream.
228         final boolean wasInProgress = mInProgress;
229         mFocusX = focusX;
230         mFocusY = focusY;
231         if (mInProgress && (span == 0 || configChanged)) {
232             mListener.onScaleEnd(this);
233             mInProgress = false;
234             mInitialSpan = span;
235         }
236         if (configChanged) {
237             mPrevSpanX = mCurrSpanX = spanX;
238             mPrevSpanY = mCurrSpanY = spanY;
239             mInitialSpan = mPrevSpan = mCurrSpan = span;
240         }
241         if (!mInProgress && span != 0 &&
242                 (wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {
243             mPrevSpanX = mCurrSpanX = spanX;
244             mPrevSpanY = mCurrSpanY = spanY;
245             mPrevSpan = mCurrSpan = span;
246             mInProgress = mListener.onScaleBegin(this);
247         }
248
249         // Handle motion; focal point and span/scale factor are changing.
250         if (action == MotionEvent.ACTION_MOVE) {
251             mCurrSpanX = spanX;
252             mCurrSpanY = spanY;
253             mCurrSpan = span;
254
255             boolean updatePrev = true;
256             if (mInProgress) {
257                 updatePrev = mListener.onScale(this);
258             }
259
260             if (updatePrev) {
261                 mPrevSpanX = mCurrSpanX;
262                 mPrevSpanY = mCurrSpanY;
263                 mPrevSpan = mCurrSpan;
264             }
265         }
266
267         return true;
268     }
269
270     /**
271      * Returns {@code true} if a scale gesture is in progress.
272      */
273     public boolean isInProgress() {
274         return mInProgress;
275     }
276
277     /**
278      * Get the X coordinate of the current gesture's focal point.
279      * If a gesture is in progress, the focal point is between
280      * each of the pointers forming the gesture.
281      *
282      * If {@link #isInProgress()} would return false, the result of this
283      * function is undefined.
284      *
285      * @return X coordinate of the focal point in pixels.
286      */
287     public float getFocusX() {
288         return mFocusX;
289     }
290
291     /**
292      * Get the Y coordinate of the current gesture's focal point.
293      * If a gesture is in progress, the focal point is between
294      * each of the pointers forming the gesture.
295      *
296      * If {@link #isInProgress()} would return false, the result of this
297      * function is undefined.
298      *
299      * @return Y coordinate of the focal point in pixels.
300      */
301     public float getFocusY() {
302         return mFocusY;
303     }
304
305     /**
306      * Return the average distance between each of the pointers forming the
307      * gesture in progress through the focal point.
308      *
309      * @return Distance between pointers in pixels.
310      */
311     public float getCurrentSpan() {
312         return mCurrSpan;
313     }
314
315     /**
316      * Return the average X distance between each of the pointers forming the
317      * gesture in progress through the focal point.
318      *
319      * @return Distance between pointers in pixels.
320      */
321     public float getCurrentSpanX() {
322         return mCurrSpanX;
323     }
324
325     /**
326      * Return the average Y distance between each of the pointers forming the
327      * gesture in progress through the focal point.
328      *
329      * @return Distance between pointers in pixels.
330      */
331     public float getCurrentSpanY() {
332         return mCurrSpanY;
333     }
334
335     /**
336      * Return the previous average distance between each of the pointers forming the
337      * gesture in progress through the focal point.
338      *
339      * @return Previous distance between pointers in pixels.
340      */
341     public float getPreviousSpan() {
342         return mPrevSpan;
343     }
344
345     /**
346      * Return the previous average X distance between each of the pointers forming the
347      * gesture in progress through the focal point.
348      *
349      * @return Previous distance between pointers in pixels.
350      */
351     public float getPreviousSpanX() {
352         return mPrevSpanX;
353     }
354
355     /**
356      * Return the previous average Y distance between each of the pointers forming the
357      * gesture in progress through the focal point.
358      *
359      * @return Previous distance between pointers in pixels.
360      */
361     public float getPreviousSpanY() {
362         return mPrevSpanY;
363     }
364
365     /**
366      * Return the scaling factor from the previous scale event to the current
367      * event. This value is defined as
368      * ({@link #getCurrentSpan()} / {@link #getPreviousSpan()}).
369      *
370      * @return The current scaling factor.
371      */
372     public float getScaleFactor() {
373         return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
374     }
375
376     /**
377      * Return the time difference in milliseconds between the previous
378      * accepted scaling event and the current scaling event.
379      *
380      * @return Time difference since the last scaling event in milliseconds.
381      */
382     public long getTimeDelta() {
383         return mCurrTime - mPrevTime;
384     }
385
386     /**
387      * Return the event time of the current event being processed.
388      *
389      * @return Current event time in milliseconds.
390      */
391     public long getEventTime() {
392         return mCurrTime;
393     }
394 }