/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view; import android.content.Context; import android.content.res.Resources; import android.os.SystemClock; import android.util.FloatMath; /** * 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: *
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.
* * @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); } final int action = event.getActionMasked(); 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; mInitialSpan = 0; } if (streamComplete) { clearTouchHistory(); return true; } } 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; addTouchHistory(event); // Determine average deviation from focal point float devSumX = 0, devSumY = 0; for (int i = 0; i < count; i++) { if (skipIndex == i) continue; // Convert the resulting diameter into a radius. final float touchSize = mTouchHistoryLastAccepted / 2; devSumX += Math.abs(event.getX(i) - focusX) + touchSize; devSumY += Math.abs(event.getY(i) - focusY) + touchSize; } 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. final boolean wasInProgress = mInProgress; mFocusX = focusX; mFocusY = focusY; if (mInProgress && (span < mMinSpan || configChanged)) { mListener.onScaleEnd(this); mInProgress = false; mInitialSpan = span; } if (configChanged) { mPrevSpanX = mCurrSpanX = spanX; mPrevSpanY = mCurrSpanY = spanY; mInitialSpan = mPrevSpan = mCurrSpan = span; } if (!mInProgress && span >= mMinSpan && (wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) { mPrevSpanX = mCurrSpanX = spanX; mPrevSpanY = mCurrSpanY = spanY; mPrevSpan = mCurrSpan = span; 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; boolean updatePrev = true; if (mInProgress) { updatePrev = mListener.onScale(this); } if (updatePrev) { mPrevSpanX = mCurrSpanX; mPrevSpanY = mCurrSpanY; mPrevSpan = mCurrSpan; } } return true; } /** * Returns {@code true} if a scale gesture is in progress. */ public boolean isInProgress() { return mInProgress; } /** * Get the X coordinate of the current gesture's focal point. * 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. * * @return X coordinate of the focal point in pixels. */ public float getFocusX() { return mFocusX; } /** * Get the Y coordinate of the current gesture's focal point. * 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. * * @return Y coordinate of the focal point in pixels. */ public float getFocusY() { return mFocusY; } /** * 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() { return mCurrSpan; } /** * 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 mCurrSpanX; } /** * 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 mCurrSpanY; } /** * 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() { return mPrevSpan; } /** * 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 mPrevSpanX; } /** * 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 mPrevSpanY; } /** * Return the scaling factor from the previous scale event to the current * event. This value is defined as * ({@link #getCurrentSpan()} / {@link #getPreviousSpan()}). * * @return The current scaling factor. */ public float getScaleFactor() { return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1; } /** * Return the time difference in milliseconds between the previous * accepted scaling event and the current scaling event. * * @return Time difference since the last scaling event in milliseconds. */ public long getTimeDelta() { return mCurrTime - mPrevTime; } /** * Return the event time of the current event being processed. * * @return Current event time in milliseconds. */ public long getEventTime() { return mCurrTime; } }