Fields Summary |
---|
private static final String | TAG |
private final android.content.Context | mContext |
private final OnScaleGestureListener | mListener |
private float | mFocusX |
private float | mFocusY |
private boolean | mQuickScaleEnabled |
private float | mCurrSpan |
private float | mPrevSpan |
private float | mInitialSpan |
private float | mCurrSpanX |
private float | mCurrSpanY |
private float | mPrevSpanX |
private float | mPrevSpanY |
private long | mCurrTime |
private long | mPrevTime |
private boolean | mInProgress |
private int | mSpanSlop |
private int | mMinSpan |
private float | mTouchUpper |
private float | mTouchLower |
private float | mTouchHistoryLastAccepted |
private int | mTouchHistoryDirection |
private long | mTouchHistoryLastAcceptedTime |
private int | mTouchMinMajor |
private MotionEvent | mDoubleTapEvent |
private int | mDoubleTapMode |
private final android.os.Handler | mHandler |
private static final long | TOUCH_STABILIZE_TIME |
private static final int | DOUBLE_TAP_MODE_NONE |
private static final int | DOUBLE_TAP_MODE_IN_PROGRESS |
private static final float | SCALE_FACTOR |
private final InputEventConsistencyVerifier | mInputEventConsistencyVerifierConsistency verifier for debugging purposes. |
private GestureDetector | mGestureDetector |
private boolean | mEventBeforeOrAboveStartingGestureEvent |
Methods Summary |
---|
private void | addTouchHistory(MotionEvent ev)The touchMajor/touchMinor elements of a MotionEvent can flutter/jitter on
some hardware/driver combos. Smooth it out to get kinder, gentler behavior.
final long currentTime = SystemClock.uptimeMillis();
final int count = ev.getPointerCount();
boolean accept = currentTime - mTouchHistoryLastAcceptedTime >= TOUCH_STABILIZE_TIME;
float total = 0;
int sampleCount = 0;
for (int i = 0; i < count; i++) {
final boolean hasLastAccepted = !Float.isNaN(mTouchHistoryLastAccepted);
final int historySize = ev.getHistorySize();
final int pointerSampleCount = historySize + 1;
for (int h = 0; h < pointerSampleCount; h++) {
float major;
if (h < historySize) {
major = ev.getHistoricalTouchMajor(i, h);
} else {
major = ev.getTouchMajor(i);
}
if (major < mTouchMinMajor) major = mTouchMinMajor;
total += major;
if (Float.isNaN(mTouchUpper) || major > mTouchUpper) {
mTouchUpper = major;
}
if (Float.isNaN(mTouchLower) || major < mTouchLower) {
mTouchLower = major;
}
if (hasLastAccepted) {
final int directionSig = (int) Math.signum(major - mTouchHistoryLastAccepted);
if (directionSig != mTouchHistoryDirection ||
(directionSig == 0 && mTouchHistoryDirection == 0)) {
mTouchHistoryDirection = directionSig;
final long time = h < historySize ? ev.getHistoricalEventTime(h)
: ev.getEventTime();
mTouchHistoryLastAcceptedTime = time;
accept = false;
}
}
}
sampleCount += pointerSampleCount;
}
final float avg = total / sampleCount;
if (accept) {
float newAccepted = (mTouchUpper + mTouchLower + avg) / 3;
mTouchUpper = (mTouchUpper + newAccepted) / 2;
mTouchLower = (mTouchLower + newAccepted) / 2;
mTouchHistoryLastAccepted = newAccepted;
mTouchHistoryDirection = 0;
mTouchHistoryLastAcceptedTime = ev.getEventTime();
}
|
private void | clearTouchHistory()Clear all touch history tracking. Useful in ACTION_CANCEL or ACTION_UP.
mTouchUpper = Float.NaN;
mTouchLower = Float.NaN;
mTouchHistoryLastAccepted = Float.NaN;
mTouchHistoryDirection = 0;
mTouchHistoryLastAcceptedTime = 0;
|
public float | getCurrentSpan()Return the average distance between each of the pointers forming the
gesture in progress through the focal point.
return mCurrSpan;
|
public float | getCurrentSpanX()Return the average X distance between each of the pointers forming the
gesture in progress through the focal point.
return mCurrSpanX;
|
public float | getCurrentSpanY()Return the average Y distance between each of the pointers forming the
gesture in progress through the focal point.
return mCurrSpanY;
|
public long | getEventTime()Return the event time of the current event being processed.
return mCurrTime;
|
public float | getFocusX()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 mFocusX;
|
public float | getFocusY()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 mFocusY;
|
public float | getPreviousSpan()Return the previous average distance between each of the pointers forming the
gesture in progress through the focal point.
return mPrevSpan;
|
public float | getPreviousSpanX()Return the previous average X distance between each of the pointers forming the
gesture in progress through the focal point.
return mPrevSpanX;
|
public float | getPreviousSpanY()Return the previous average Y distance between each of the pointers forming the
gesture in progress through the focal point.
return mPrevSpanY;
|
public float | getScaleFactor()Return the scaling factor from the previous scale event to the current
event. This value is defined as
({@link #getCurrentSpan()} / {@link #getPreviousSpan()}).
if (inDoubleTapMode()) {
// 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
final boolean scaleUp =
(mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan < mPrevSpan)) ||
(!mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan > mPrevSpan));
final float spanDiff = (Math.abs(1 - (mCurrSpan / mPrevSpan)) * SCALE_FACTOR);
return mPrevSpan <= 0 ? 1 : scaleUp ? (1 + spanDiff) : (1 - spanDiff);
}
return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
|
public long | getTimeDelta()Return the time difference in milliseconds between the previous
accepted scaling event and the current scaling event.
return mCurrTime - mPrevTime;
|
private boolean | inDoubleTapMode()
return mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS;
|
public boolean | isInProgress()Returns {@code true} if a scale gesture is in progress.
return mInProgress;
|
public boolean | isQuickScaleEnabled()Return whether the quick scale gesture, in which the user performs a double tap followed by a
swipe, should perform scaling. {@see #setQuickScaleEnabled(boolean)}.
return mQuickScaleEnabled;
|
public boolean | onTouchEvent(MotionEvent event)Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener}
when appropriate.
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.
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
mCurrTime = event.getEventTime();
final int action = event.getActionMasked();
// Forward the event to check for double tap gesture
if (mQuickScaleEnabled) {
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.
// This means the app probably didn't give us all the events. Shame on it.
if (mInProgress) {
mListener.onScaleEnd(this);
mInProgress = false;
mInitialSpan = 0;
mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
} else if (mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS && streamComplete) {
mInProgress = false;
mInitialSpan = 0;
mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
}
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();
final int div = pointerUp ? count - 1 : count;
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);
// 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;
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
// a fresh scale event stream.
final boolean wasInProgress = mInProgress;
mFocusX = focusX;
mFocusY = focusY;
if (!inDoubleTapMode() && mInProgress && (span < mMinSpan || configChanged)) {
mListener.onScaleEnd(this);
mInProgress = false;
mInitialSpan = span;
mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
}
if (configChanged) {
mPrevSpanX = mCurrSpanX = spanX;
mPrevSpanY = mCurrSpanY = spanY;
mInitialSpan = mPrevSpan = mCurrSpan = span;
}
final int minSpan = inDoubleTapMode() ? mSpanSlop : mMinSpan;
if (!mInProgress && span >= minSpan &&
(wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {
mPrevSpanX = mCurrSpanX = spanX;
mPrevSpanY = mCurrSpanY = spanY;
mPrevSpan = mCurrSpan = span;
mPrevTime = mCurrTime;
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;
mPrevTime = mCurrTime;
}
}
return true;
|
public void | setQuickScaleEnabled(boolean scales)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.
mQuickScaleEnabled = scales;
if (mQuickScaleEnabled && 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);
}
|