1、前提介绍:
关于Android 手势识别就是当前view 根据用户的不同touch行为,给出不同的处理结果。这里我介绍一下我自己做的一些手势识别如下。
2、单指相关
2.1 单指点击、长按、拖动、左滑、右滑、上滑、下滑,在frameworks/base/core/java/android/view/GestureDetector.java Android原生代码就已经实现了。我们使用的时候只需要继承 SimpleOnGestureListener class,然后通过调用 new GestureDetector(context, new SimpleGesture(), null, true /* ignoreMultitouch */); 的时候将继承自 SimpleOnGestureListener 的class 作为参数传递给GestureDetector就可以了。
/*
* Copyright (C) 2008 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.os.Handler;
import android.os.Message;
/**
* Detects various gestures and events using the supplied {@link MotionEvent}s.
* The {@link OnGestureListener} callback will notify users when a particular
* motion event has occurred. This class should only be used with {@link MotionEvent}s
* reported via touch (don't use for trackball events).
*
* To use this class:
* <ul>
* <li>Create an instance of the {@code GestureDetector} for your {@link View}
* <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
* {@link #onTouchEvent(MotionEvent)}. The methods defined in your callback
* will be executed when the events occur.
* <li>If listening for {@link OnContextClickListener#onContextClick(MotionEvent)}
* you must call {@link #onGenericMotionEvent(MotionEvent)}
* in {@link View#onGenericMotionEvent(MotionEvent)}.
* </ul>
*/
public class GestureDetector {
/**
* The listener that is used to notify when gestures occur.
* If you want to listen for all the different gestures then implement
* this interface. If you only want to listen for a subset it might
* be easier to extend {@link SimpleOnGestureListener}.
*/
public interface OnGestureListener {
/**
* Notified when a tap occurs with the down {@link MotionEvent}
* that triggered it. This will be triggered immediately for
* every down event. All other events should be preceded by this.
*
* @param e The down motion event.
*/
boolean onDown(MotionEvent e); // 接收down事件
/**
* The user has performed a down {@link MotionEvent} and not performed
* a move or up yet. This event is commonly used to provide visual
* feedback to the user to let them know that their action has been
* recognized i.e. highlight an element.
*
* @param e The down motion event
*/
void onShowPress(MotionEvent e);
/**
* Notified when a tap occurs with the up {@link MotionEvent}
* that triggered it.
*
* @param e The up motion event that completed the first tap
* @return true if the event is consumed, else false
*/
boolean onSingleTapUp(MotionEvent e);
/**
* Notified when a scroll occurs with the initial on down {@link MotionEvent} and the
* current move {@link MotionEvent}. The distance in x and y is also supplied for
* convenience.
*
* @param e1 The first down motion event that started the scrolling.
* @param e2 The move motion event that triggered the current onScroll.
* @param distanceX The distance along the X axis that has been scrolled since the last
* call to onScroll. This is NOT the distance between {@code e1}
* and {@code e2}.
* @param distanceY The distance along the Y axis that has been scrolled since the last
* call to onScroll. This is NOT the distance between {@code e1}
* and {@code e2}.
* @return true if the event is consumed, else false
*/
boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);//通过这个方法可以监听拖动事件
/**
* Notified when a long press occurs with the initial on down {@link MotionEvent}
* that trigged it.
*
* @param e The initial on down motion event that started the longpress.
*/
void onLongPress(MotionEvent e); // 通过这个方法可以监听长按事件,长按的时间可以通过Android提供的接口来设定
/**
* Notified of a fling event when it occurs with the initial on down {@link MotionEvent}
* and the matching up {@link MotionEvent}. The calculated velocity is supplied along
* the x and y axis in pixels per second.
*
* @param e1 The first down motion event that started the fling.
* @param e2 The move motion event that triggered the current onFling.
* @param velocityX The velocity of this fling measured in pixels per second
* along the x axis.
* @param velocityY The velocity of this fling measured in pixels per second
* along the y axis.
* @return true if the event is consumed, else false
*/
boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY); // 通过这个方法判断滑动,上面的onScroll是提供实时的,这个onFling是判断滑动结果的。
}
/**
* The listener that is used to notify when a double-tap or a confirmed
* single-tap occur.
*/
public interface OnDoubleTapListener {
/**
* Notified when a single-tap occurs.
* <p>
* Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this
* will only be called after the detector is confident that the user's
* first tap is not followed by a second tap leading to a double-tap
* gesture.
*
* @param e The down motion event of the single-tap.
* @return true if the event is consumed, else false
*/
boolean onSingleTapConfirmed(MotionEvent e); // 点击的确认
/**
* Notified when a double-tap occurs.
*
* @param e The down motion event of the first tap of the double-tap.
* @return true if the event is consumed, else false
*/
boolean onDoubleTap(MotionEvent e); // 双击,只通知双击结果
/**
* Notified when an event within a double-tap gesture occurs, including
* the down, move, and up events.
*
* @param e The motion event that occurred during the double-tap gesture.
* @return true if the event is consumed, else false
*/
boolean onDoubleTapEvent(MotionEvent e); // 这个也是双击,但是包括了双击的 down, move, and up events.我理解就是第二次touch的 down, move, and up events
}
/**
* The listener that is used to notify when a context click occurs. When listening for a
* context click ensure that you call {@link #onGenericMotionEvent(MotionEvent)} in
* {@link View#onGenericMotionEvent(MotionEvent)}.
*/
public interface OnContextClickListener {
/**
* Notified when a context click occurs.
*
* @param e The motion event that occurred during the context click.
* @return true if the event is consumed, else false
*/
boolean onContextClick(MotionEvent e);
}
/**
* A convenience class to extend when you only want to listen for a subset
* of all the gestures. This implements all methods in the
* {@link OnGestureListener}, {@link OnDoubleTapListener}, and {@link OnContextClickListener}
* but does nothing and return {@code false} for all applicable methods.
*/
这个class就是我们说的,继承了OnGestureListener, OnDoubleTapListener,实现了单指的基本功能。
public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener,
OnContextClickListener {
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
public void onLongPress(MotionEvent e) {
}
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
return false;
}
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
return false;
}
public void onShowPress(MotionEvent e) {
}
public boolean onDown(MotionEvent e) {
return false;
}
public boolean onDoubleTap(MotionEvent e) {
return false;
}
public boolean onDoubleTapEvent(MotionEvent e) {
return false;
}
public boolean onSingleTapConfirmed(MotionEvent e) {
return false;
}
public boolean onContextClick(MotionEvent e) {
return false;
}
}
private int mTouchSlopSquare;
private int mDoubleTapTouchSlopSquare;
private int mDoubleTapSlopSquare;
private int mMinimumFlingVelocity;
private int mMaximumFlingVelocity;
private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
private static final int DOUBLE_TAP_MIN_TIME = ViewConfiguration.getDoubleTapMinTime();
// constants for Message.what used by GestureHandler below
private static final int SHOW_PRESS = 1;
private static final int LONG_PRESS = 2;
private static final int TAP = 3;
private final Handler mHandler;
private final OnGestureListener mListener;
private OnDoubleTapListener mDoubleTapListener;
private OnContextClickListener mContextClickListener;
private boolean mStillDown;
private boolean mDeferConfirmSingleTap;
private boolean mInLongPress;
private boolean mInContextClick;
private boolean mAlwaysInTapRegion;
private boolean mAlwaysInBiggerTapRegion;
private boolean mIgnoreNextUpEvent;
private MotionEvent mCurrentDownEvent;
private MotionEvent mPreviousUpEvent;
/**
* True when the user is still touching for the second tap (down, move, and
* up events). Can only be true if there is a double tap listener attached.
*/
private boolean mIsDoubleTapping;
private float mLastFocusX;
private float mLastFocusY;
private float mDownFocusX;
private float mDownFocusY;
private boolean mIsLongpressEnabled;
/**
* Determines speed during touch scrolling
*/
private VelocityTracker mVelocityTracker;
/**
* Consistency verifier for debugging purposes.
*/
private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
InputEventConsistencyVerifier.isInstrumentationEnabled() ?
new InputEventConsistencyVerifier(this, 0) : null;
private class GestureHandler extends Handler {
GestureHandler() {
super();
}
GestureHandler(Handler handler) {
super(handler.getLooper());
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_PRESS:
mListener.onShowPress(mCurrentDownEvent);
break;
case LONG_PRESS:
dispatchLongPress();
break;
case TAP:
// If the user's finger is still down, do not count it as a tap
if (mDoubleTapListener != null) {
if (!mStillDown) {
mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
} else {
mDeferConfirmSingleTap = true;
}
}
break;
default:
throw new RuntimeException("Unknown message " + msg); //never
}
}
}
/**
* Creates a GestureDetector with the supplied listener.
* This variant of the constructor should be used from a non-UI thread
* (as it allows specifying the Handler).
*
* @param listener the listener invoked for all the callbacks, this must
* not be null.
* @param handler the handler to use
*
* @throws NullPointerException if either {@code listener} or
* {@code handler} is null.
*
* @deprecated Use {@link #GestureDetector(android.content.Context,
* android.view.GestureDetector.OnGestureListener, android.os.Handler)} instead.
*/
@Deprecated
public GestureDetector(OnGestureListener listener, Handler handler) {
this(null, listener, handler);
}
/**
* Creates a GestureDetector with the supplied listener.
* You may only use this constructor from a UI thread (this is the usual situation).
* @see android.os.Handler#Handler()
*
* @param listener the listener invoked for all the callbacks, this must
* not be null.
*
* @throws NullPointerException if {@code listener} is null.
*
* @deprecated Use {@link #GestureDetector(android.content.Context,
* android.view.GestureDetector.OnGestureListener)} instead.
*/
@Deprecated
public GestureDetector(OnGestureListener listener) {
this(null, listener, null);
}
/**
* Creates a GestureDetector with the supplied listener.
* You may only use this constructor from a {@link android.os.Looper} thread.
* @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.
*
* @throws NullPointerException if {@code listener} is null.
*/
public GestureDetector(Context context, OnGestureListener listener) {
this(context, listener, null);
}
/**
* Creates a GestureDetector with the supplied listener that runs deferred events on the
* thread associated with the supplied {@link android.os.Handler}.
* @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 GestureDetector(Context context, OnGestureListener listener, Handler handler) {
if (handler != null) {
mHandler = new GestureHandler(handler);
} else {
mHandler = new GestureHandler();
}
mListener = listener;
if (listener instanceof OnDoubleTapListener) {
setOnDoubleTapListener((OnDoubleTapListener) listener);
}
if (listener instanceof OnContextClickListener) {
setContextClickListener((OnContextClickListener) listener);
}
init(context);
}
/**
* Creates a GestureDetector with the supplied listener that runs deferred events on the
* thread associated with the supplied {@link android.os.Handler}.
* @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.
* @param unused currently not used.
*
* @throws NullPointerException if {@code listener} is null.
*/
public GestureDetector(Context context, OnGestureListener listener, Handler handler,
boolean unused) {
this(context, listener, handler);
}
private void init(Context context) {
if (mListener == null) {
throw new NullPointerException("OnGestureListener must not be null");
}
mIsLongpressEnabled = true;
// Fallback to support pre-donuts releases
int touchSlop, doubleTapSlop, doubleTapTouchSlop;
if (context == null) {
//noinspection deprecation
touchSlop = ViewConfiguration.getTouchSlop();
doubleTapTouchSlop = touchSlop; // Hack rather than adding a hiden method for this
doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
//noinspection deprecation
mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
} else {
final ViewConfiguration configuration = ViewConfiguration.get(context);
touchSlop = configuration.getScaledTouchSlop();
doubleTapTouchSlop = configuration.getScaledDoubleTapTouchSlop();
doubleTapSlop = configuration.getScaledDoubleTapSlop();
mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
}
mTouchSlopSquare = touchSlop * touchSlop;
mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop;
mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
}
/**
* Sets the listener which will be called for double-tap and related
* gestures.
*
* @param onDoubleTapListener the listener invoked for all the callbacks, or
* null to stop listening for double-tap gestures.
*/
public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {
mDoubleTapListener = onDoubleTapListener;
}
/**
* Sets the listener which will be called for context clicks.
*
* @param onContextClickListener the listener invoked for all the callbacks, or null to stop
* listening for context clicks.
*/
public void setContextClickListener(OnContextClickListener onContextClickListener) {
mContextClickListener = onContextClickListener;
}
/**
* Set whether longpress is enabled, if this is enabled when a user
* presses and holds down you get a longpress event and nothing further.
* If it's disabled the user can press and hold down and then later
* moved their finger and you will get scroll events. By default
* longpress is enabled.
*
* @param isLongpressEnabled whether longpress should be enabled.
*/
public void setIsLongpressEnabled(boolean isLongpressEnabled) {
mIsLongpressEnabled = isLongpressEnabled;
}
/**
* @return true if longpress is enabled, else false.
*/
public boolean isLongpressEnabled() {
return mIsLongpressEnabled;
}
/**
* Analyzes the given motion event and if applicable triggers the
* appropriate callbacks on the {@link OnGestureListener} supplied.
*
* @param ev The current motion event.
* @return true if the {@link OnGestureListener} consumed the event,
* else false.
*/
public boolean onTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 0);
}
final int action = ev.getAction();
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
final boolean pointerUp =
(action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;
final int skipIndex = pointerUp ? ev.getActionIndex() : -1;
final boolean isGeneratedGesture =
(ev.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;
// Determine focal point
float sumX = 0, sumY = 0;
final int count = ev.getPointerCount();
for (int i = 0; i < count; i++) {
if (skipIndex == i) continue;
sumX += ev.getX(i);
sumY += ev.getY(i);
}
final int div = pointerUp ? count - 1 : count;
final float focusX = sumX / div;
final float focusY = sumY / div;
boolean handled = false;
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_POINTER_DOWN:
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
// Cancel long press and taps
cancelTaps();
break;
case MotionEvent.ACTION_POINTER_UP:
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
// Check the dot product of current velocities.
// If the pointer that left was opposing another velocity vector, clear.
mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
final int upIndex = ev.getActionIndex();
final int id1 = ev.getPointerId(upIndex);
final float x1 = mVelocityTracker.getXVelocity(id1);
final float y1 = mVelocityTracker.getYVelocity(id1);
for (int i = 0; i < count; i++) {
if (i == upIndex) continue;
final int id2 = ev.getPointerId(i);
final float x = x1 * mVelocityTracker.getXVelocity(id2);
final float y = y1 * mVelocityTracker.getYVelocity(id2);
final float dot = x + y;
if (dot < 0) {
mVelocityTracker.clear();
break;
}
}
break;
case MotionEvent.ACTION_DOWN:
if (mDoubleTapListener != null) {
boolean hadTapMessage = mHandler.hasMessages(TAP);
if (hadTapMessage) mHandler.removeMessages(TAP);
if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null)
&& hadTapMessage
&& isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
// This is a second tap
mIsDoubleTapping = true;
// Give a callback with the first tap of the double-tap
handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
// Give a callback with down event of the double-tap
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else {
// This is a first tap
mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
}
}
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
if (mCurrentDownEvent != null) {
mCurrentDownEvent.recycle();
}
mCurrentDownEvent = MotionEvent.obtain(ev);
mAlwaysInTapRegion = true;
mAlwaysInBiggerTapRegion = true;
mStillDown = true;
mInLongPress = false;
mDeferConfirmSingleTap = false;
if (mIsLongpressEnabled) {
mHandler.removeMessages(LONG_PRESS);
mHandler.sendEmptyMessageAtTime(LONG_PRESS,
mCurrentDownEvent.getDownTime() + LONGPRESS_TIMEOUT);
}
mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
handled |= mListener.onDown(ev);
break;
case MotionEvent.ACTION_MOVE:
if (mInLongPress || mInContextClick) {
break;
}
final float scrollX = mLastFocusX - focusX;
final float scrollY = mLastFocusY - focusY;
if (mIsDoubleTapping) {
// Give the move events of the double-tap
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else if (mAlwaysInTapRegion) {
final int deltaX = (int) (focusX - mDownFocusX);
final int deltaY = (int) (focusY - mDownFocusY);
int distance = (deltaX * deltaX) + (deltaY * deltaY);
int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare;
if (distance > slopSquare) {
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastFocusX = focusX;
mLastFocusY = focusY;
mAlwaysInTapRegion = false;
mHandler.removeMessages(TAP);
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
}
int doubleTapSlopSquare = isGeneratedGesture ? 0 : mDoubleTapTouchSlopSquare;
if (distance > doubleTapSlopSquare) {
mAlwaysInBiggerTapRegion = false;
}
} else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastFocusX = focusX;
mLastFocusY = focusY;
}
break;
case MotionEvent.ACTION_UP:
mStillDown = false;
MotionEvent currentUpEvent = MotionEvent.obtain(ev);
if (mIsDoubleTapping) {
// Finally, give the up event of the double-tap
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else if (mInLongPress) {
mHandler.removeMessages(TAP);
mInLongPress = false;
} else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
handled = mListener.onSingleTapUp(ev);
if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
mDoubleTapListener.onSingleTapConfirmed(ev);
}
} else if (!mIgnoreNextUpEvent) {
// A fling must travel the minimum tap distance
final VelocityTracker velocityTracker = mVelocityTracker;
final int pointerId = ev.getPointerId(0);
velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
final float velocityY = velocityTracker.getYVelocity(pointerId);
final float velocityX = velocityTracker.getXVelocity(pointerId);
if ((Math.abs(velocityY) > mMinimumFlingVelocity)
|| (Math.abs(velocityX) > mMinimumFlingVelocity)) {
handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);
}
}
if (mPreviousUpEvent != null) {
mPreviousUpEvent.recycle();
}
// Hold the event we obtained above - listeners may have changed the original.
mPreviousUpEvent = currentUpEvent;
if (mVelocityTracker != null) {
// This may have been cleared when we called out to the
// application above.
mVelocityTracker.recycle();
mVelocityTracker = null;
}
mIsDoubleTapping = false;
mDeferConfirmSingleTap = false;
mIgnoreNextUpEvent = false;
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
break;
case MotionEvent.ACTION_CANCEL:
cancel();
break;
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
}
return handled;
}
/**
* Analyzes the given generic motion event and if applicable triggers the
* appropriate callbacks on the {@link OnGestureListener} supplied.
*
* @param ev The current motion event.
* @return true if the {@link OnGestureListener} consumed the event,
* else false.
*/
public boolean onGenericMotionEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onGenericMotionEvent(ev, 0);
}
final int actionButton = ev.getActionButton();
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_BUTTON_PRESS:
if (mContextClickListener != null && !mInContextClick && !mInLongPress
&& (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
|| actionButton == MotionEvent.BUTTON_SECONDARY)) {
if (mContextClickListener.onContextClick(ev)) {
mInContextClick = true;
mHandler.removeMessages(LONG_PRESS);
mHandler.removeMessages(TAP);
return true;
}
}
break;
case MotionEvent.ACTION_BUTTON_RELEASE:
if (mInContextClick && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
|| actionButton == MotionEvent.BUTTON_SECONDARY)) {
mInContextClick = false;
mIgnoreNextUpEvent = true;
}
break;
}
return false;
}
private void cancel() {
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
mHandler.removeMessages(TAP);
mVelocityTracker.recycle();
mVelocityTracker = null;
mIsDoubleTapping = false;
mStillDown = false;
mAlwaysInTapRegion = false;
mAlwaysInBiggerTapRegion = false;
mDeferConfirmSingleTap = false;
mInLongPress = false;
mInContextClick = false;
mIgnoreNextUpEvent = false;
}
private void cancelTaps() {
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
mHandler.removeMessages(TAP);
mIsDoubleTapping = false;
mAlwaysInTapRegion = false;
mAlwaysInBiggerTapRegion = false;
mDeferConfirmSingleTap = false;
mInLongPress = false;
mInContextClick = false;
mIgnoreNextUpEvent = false;
}
private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
MotionEvent secondDown) {
if (!mAlwaysInBiggerTapRegion) {
return false;
}
final long deltaTime = secondDown.getEventTime() - firstUp.getEventTime();
if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) {
return false;
}
int deltaX = (int) firstDown.getX() - (int) secondDown.getX();
int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
final boolean isGeneratedGesture =
(firstDown.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;
int slopSquare = isGeneratedGesture ? 0 : mDoubleTapSlopSquare;
return (deltaX * deltaX + deltaY * deltaY < slopSquare);
}
private void dispatchLongPress() {
mHandler.removeMessages(TAP);
mDeferConfirmSingleTap = false;
mInLongPress = true;
mListener.onLongPress(mCurrentDownEvent);
}
}
2、双指缩放
这个android 原生也实现了,在 frameworks/base/core/java/android/view/ScaleGestureDetector.java里面。我们用的时候只需要去实现ScaleGestureDetector.SimpleOnScaleGestureListener的方法就可以。
/*
* 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.Build;
import android.os.Handler;
/**
* 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:
* <ul>
* <li>Create an instance of the {@code ScaleGestureDetector} for your
* {@link View}
* <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
* {@link #onTouchEvent(MotionEvent)}. The methods defined in your
* callback will be executed when the events occur.
* </ul>
*/
public class ScaleGestureDetector {
private static final String TAG = "ScaleGestureDetector";
/**
* The listener for receiving notifications when gestures occur.
* If you want to listen for all the different gestures then implement
* this interface. If you only want to listen for a subset it might
* be easier to extend {@link SimpleOnScaleGestureListener}.
*
* An application will receive events in the following order:
* <ul>
* <li>One {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)}
* <li>Zero or more {@link OnScaleGestureListener#onScale(ScaleGestureDetector)}
* <li>One {@link OnScaleGestureListener#onScaleEnd(ScaleGestureDetector)}
* </ul>
*/
public interface OnScaleGestureListener {
/**
* Responds to scaling events for a gesture in progress.
* Reported by pointer motion.
*
* @param detector The detector reporting the event - use this to
* retrieve extended info about event state.
* @return Whether or not the detector should consider this event
* as handled. If an event was not handled, the detector
* will continue to accumulate movement until an event is
* handled. This can be useful if an application, for example,
* only wants to update scaling factors if the change is
* greater than 0.01.
*/
public boolean onScale(ScaleGestureDetector detector);
/**
* Responds to the beginning of a scaling gesture. Reported by
* new pointers going down.
*
* @param detector The detector reporting the event - use this to
* retrieve extended info about event state.
* @return Whether or not the detector should continue recognizing
* this gesture. For example, if a gesture is beginning
* with a focal point outside of a region where it makes
* sense, onScaleBegin() may return false to ignore the
* rest of the gesture.
*/
public boolean onScaleBegin(ScaleGestureDetector detector);
/**
* Responds to the end of a scale gesture. Reported by existing
* pointers going up.
*
* Once a scale has ended, {@link ScaleGestureDetector#getFocusX()}
* and {@link ScaleGestureDetector#getFocusY()} will return focal point
* of the pointers remaining on the screen.
*
* @param detector The detector reporting the event - use this to
* retrieve extended info about event state.
*/
public void onScaleEnd(ScaleGestureDetector detector);
}
/**
* A convenience class to extend when you only want to listen for a subset
* of scaling-related events. This implements all methods in
* {@link OnScaleGestureListener} but does nothing.
* {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} returns
* {@code false} so that a subclass can retrieve the accumulated scale
* factor in an overridden onScaleEnd.
* {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} returns
* {@code true}.
*/
public static class SimpleOnScaleGestureListener implements OnScaleGestureListener {
public boolean onScale(ScaleGestureDetector detector) { // 缩放的过程
return false;
}
public boolean onScaleBegin(ScaleGestureDetector detector) { // 缩放开始
return true;
}
public void onScaleEnd(ScaleGestureDetector detector) { // 缩放结束
// Intentionally empty
}
}
private final Context mContext;
private final OnScaleGestureListener mListener;
private float mFocusX;
private float mFocusY;
private boolean mQuickScaleEnabled;
private boolean mStylusScaleEnabled;
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 final Handler mHandler;
private float mAnchoredScaleStartX;
private float mAnchoredScaleStartY;
private int mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
private static final long TOUCH_STABILIZE_TIME = 128; // ms
private static final float SCALE_FACTOR = .5f;
private static final int ANCHORED_SCALE_MODE_NONE = 0;
private static final int ANCHORED_SCALE_MODE_DOUBLE_TAP = 1;
private static final int ANCHORED_SCALE_MODE_STYLUS = 2;
/**
* Consistency verifier for debugging purposes.
*/
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;
final Resources res = context.getResources();
mMinSpan = res.getDimensionPixelSize(com.android.internal.R.dimen.config_minScalingSpan);
mHandler = handler;
// Quick scale is enabled by default after JB_MR2
final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
if (targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN_MR2) {
setQuickScaleEnabled(true);
}
// Stylus scale is enabled by default after LOLLIPOP_MR1
if (targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
setStylusScaleEnabled(true);
}
}
/**
* Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener}
* when appropriate.
*
* <p>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.</p>
*
* @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);
}
mCurrTime = event.getEventTime();
final int action = event.getActionMasked();
// Forward the event to check for double tap gesture
if (mQuickScaleEnabled) {
mGestureDetector.onTouchEvent(event);
}
final int count = event.getPointerCount();
final boolean isStylusButtonDown =
(event.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;
final boolean anchoredScaleCancelled =
mAnchoredScaleMode == ANCHORED_SCALE_MODE_STYLUS && !isStylusButtonDown;
final boolean streamComplete = action == MotionEvent.ACTION_UP ||
action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;
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;
mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
} else if (inAnchoredScaleMode() && streamComplete) {
mInProgress = false;
mInitialSpan = 0;
mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
}
if (streamComplete) {
return true;
}
}
if (!mInProgress && mStylusScaleEnabled && !inAnchoredScaleMode()
&& !streamComplete && isStylusButtonDown) {
// Start of a button scale gesture
mAnchoredScaleStartX = event.getX();
mAnchoredScaleStartY = event.getY();
mAnchoredScaleMode = ANCHORED_SCALE_MODE_STYLUS;
mInitialSpan = 0;
}
final boolean configChanged = action == MotionEvent.ACTION_DOWN ||
action == MotionEvent.ACTION_POINTER_UP ||
action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled;
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 div = pointerUp ? count - 1 : count;
final float focusX;
final float focusY;
if (inAnchoredScaleMode()) {
// In anchored scale mode, the focal pt is always where the double tap
// or button down gesture started
focusX = mAnchoredScaleStartX;
focusY = mAnchoredScaleStartY;
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;
}
// 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.
devSumX += Math.abs(event.getX(i) - focusX);
devSumY += Math.abs(event.getY(i) - focusY);
}
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 (inAnchoredScaleMode()) {
span = spanY;
} else {
span = (float) Math.hypot(spanX, 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 (!inAnchoredScaleMode() && mInProgress && (span < mMinSpan || configChanged)) {
mListener.onScaleEnd(this);
mInProgress = false;
mInitialSpan = span;
}
if (configChanged) {
mPrevSpanX = mCurrSpanX = spanX;
mPrevSpanY = mCurrSpanY = spanY;
mInitialSpan = mPrevSpan = mCurrSpan = span;
}
final int minSpan = inAnchoredScaleMode() ? 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;
}
private boolean inAnchoredScaleMode() {
return mAnchoredScaleMode != ANCHORED_SCALE_MODE_NONE;
}
/**
* 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) {
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
mAnchoredScaleStartX = e.getX();
mAnchoredScaleStartY = e.getY();
mAnchoredScaleMode = ANCHORED_SCALE_MODE_DOUBLE_TAP;
return true;
}
};
mGestureDetector = new GestureDetector(mContext, gestureListener, mHandler);
}
}
/**
* Return whether the quick scale gesture, in which the user performs a double tap followed by a
* swipe, should perform scaling. {@see #setQuickScaleEnabled(boolean)}.
*/
public boolean isQuickScaleEnabled() {
return mQuickScaleEnabled;
}
/**
* Sets whether the associates {@link OnScaleGestureListener} should receive
* onScale callbacks when the user uses a stylus and presses the button.
* Note that this is enabled by default if the app targets API 23 and newer.
*
* @param scales true to enable stylus scaling, false to disable.
*/
public void setStylusScaleEnabled(boolean scales) {
mStylusScaleEnabled = scales;
}
/**
* Return whether the stylus scale gesture, in which the user uses a stylus and presses the
* button, should perform scaling. {@see #setStylusScaleEnabled(boolean)}
*/
public boolean isStylusScaleEnabled() {
return mStylusScaleEnabled;
}
/**
* 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() {
if (inAnchoredScaleMode()) {
// 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;
}
/**
* 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;
}
}
3、多指滑动。
我主要做的有双指和三指的滑动识别,滑动分为直接通知滑动结果和滑动过程实时通知
private class AiwaysMultiFingerSplit {
private MotionEvent mStartMutiEvent;
private MotionEvent mLastEvent;
private float mLastFocusX;
private float mLastFocusY;
private float mDownFocusX;
private float mDownFocusY;
public boolean onTouchEvent(MotionEvent ev) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
boolean handled = false;
float sumX = 0, sumY = 0;
float focusX = 0, focusY = 0;
int pCount = ev.getPointerCount();
if (pCount == 2 || pCount == 3) {
for (int i = 0; i < pCount; i++) {
sumX += ev.getX(i);
sumY += ev.getY(i);
}
focusX = sumX / pCount;
focusY = sumY / pCount;
}
switch(ev.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
break;
case MotionEvent.ACTION_UP:
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
if (mLastEvent != null) {
mLastEvent = null;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
if (pCount == 2 || pCount == 3) {
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
mStartMutiEvent = MotionEvent.obtain(ev);
mLastEvent = MotionEvent.obtain(ev);
}
handled = true;
break;
case MotionEvent.ACTION_POINTER_UP:
Log.d(TAG, "ACTION_POINTER_UP_"+ev.getActionIndex() + " ,onTouchEvent pCount:" + ev.getPointerCount());
if ((pCount-1) == 2 || (pCount-1) == 3) {
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
mStartMutiEvent = MotionEvent.obtain(ev);
}
if (pCount == 2 || pCount == 3) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
final int id1 = ev.getPointerId(ev.getActionIndex());
float sumVX = velocityTracker.getXVelocity(id1);
float sumVY = velocityTracker.getYVelocity(id1);
for (int i = 0; i < pCount; i++) {
if (i == ev.getActionIndex()) continue;
final int id2 = ev.getPointerId(i);
sumVX += velocityTracker.getXVelocity(id2);
sumVY += velocityTracker.getYVelocity(id2);
}
final float velocityX = sumVX / pCount;
final float velocityY = sumVY / pCount;
velocityTracker.clear();
// 这里的处理和onFling一样,关于手指的速度和滑动距离,开始是想把每个手指的滑动和距离都通知出去,但是过程太麻烦,所以上面计算的时候就取了平均值。
handled |= notifyMutiFingerSlipAction(mLastEvent, ev, velocityX , velocityY);
}
break;
case MotionEvent.ACTION_MOVE:
if (pCount == 2 || pCount == 3) { // 判断是2指或3指的时候直接调用notifyMutiFingerSlipProcess来处理,基本和上面的onScroll处理一样
final float scrollX = mLastFocusX - focusX;
final float scrollY = mLastFocusY - focusY;
if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
handled |= notifyMutiFingerSlipProcess(mStartMutiEvent, ev, scrollX, scrollY);
}
mLastFocusX = focusX;
mLastFocusY = focusY;
handled = true;
}
break;
default:
break;
}
return handled;
}
}
4、总体识别代码
使用的时候只需要new AiwaysGestureManager,然后 继承 AiwaysGestureListener 传进子类 就可以监听一下事件了
package wayos.car.view;
import android.content.Context;
import android.view.GestureDetector;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.util.Log;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
public class AiwaysGestureManager {
private final String TAG = "AiwaysGestureManager";
private GestureDetector mGestureDetector;
private ScaleGestureDetector mScaleDetector;
private AiwaysGestureListener mListener;
private AiwaysMultiFingerSplit mAiwaysMultiFingerSplit;
private VelocityTracker mVelocityTracker;
private int mMinimumFlingVelocity;
private int mMaximumFlingVelocity;
private int minVelocity = 50;
public interface AiwaysGestureListener {
public boolean onSingleTapUp(MotionEvent motionEvent);
public boolean onDoubleTap(MotionEvent motionEvent);
public void onLongPress(MotionEvent e);
public boolean onDown(MotionEvent e);
public boolean onUp(MotionEvent motionEvent);
public boolean onScale(ScaleGestureDetector detector);
public boolean onScaleBegin(ScaleGestureDetector detector);
public void onScaleEnd(ScaleGestureDetector detector);
public boolean singleFingeronSlipProcess(MotionEvent e1, MotionEvent e2, float dx, float dy);
public boolean singleFingerSlipAction(GestureEvent gestureEvent, MotionEvent startEvent, MotionEvent endEvent, float velocity);
public boolean mutiFingerSlipProcess(GestureEvent gestureEvent, float startX, float startY, float endX, float endY, float moveX, float moveY);
public boolean mutiFingerSlipAction(GestureEvent gestureEvent, float startX, float startY, float endX, float endY, float velocityX,float velocityY);
}
public enum GestureEvent {
SINGLE_GINGER_LEFT_SLIP,
SINGLE_GINGER_RIGHT_SLIP,
SINGLE_GINGER_UP_SLIP,
SINGLE_GINGER_DOWN_SLIP,
DOUBLE_GINGER,
DOUBLE_GINGER_LEFT_SLIP,
DOUBLE_GINGER_RIGHT_SLIP,
DOUBLE_GINGER_UP_SLIP,
DOUBLE_GINGER_DOWN_SLIP,
THREE_GINGER,
THREE_GINGER_UP_SLIP,
THREE_GINGER_DOWN_SLIP,
SCALE_START,
SCALE_MOVE,
SCALE_END;
}
public AiwaysGestureManager(Context context, AiwaysGestureListener listener) {
mListener = listener;
mGestureDetector = new GestureDetector(context, new SimpleGesture(), null, true /* ignoreMultitouch */);
mGestureDetector.setOnDoubleTapListener(new AiwaysDoubleTapListener());
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
mAiwaysMultiFingerSplit = new AiwaysMultiFingerSplit();
if (context == null) {
mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
} else {
final ViewConfiguration configuration = ViewConfiguration.get(context);
mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
}
}
public boolean onTouchEvent(MotionEvent event) {
final boolean gestureProcessed = mGestureDetector.onTouchEvent(event);
final boolean scaleProcessed = mScaleDetector.onTouchEvent(event);
final boolean multiFingerProcessed = mAiwaysMultiFingerSplit.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP) {
mListener.onUp(event);
}
return (gestureProcessed | scaleProcessed | multiFingerProcessed);
}
private class SimpleGesture extends GestureDetector.SimpleOnGestureListener {
@Override
public void onLongPress(MotionEvent e) {
mListener.onLongPress(e);
}
@Override
public boolean onScroll(
MotionEvent e1, MotionEvent e2, float dx, float dy) {
return mListener.singleFingeronSlipProcess(e1, e2, dx, dy);
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
if (e1.getX() - e2.getX() > 0 && Math.abs((int)(e1.getX() - e2.getX())) > Math.abs((int)(e1.getY() - e2.getY()))
&& Math.abs(velocityX) > minVelocity) {
return mListener.singleFingerSlipAction(GestureEvent.SINGLE_GINGER_LEFT_SLIP, e1, e2, Math.abs(velocityX));
}
if (e1.getX() - e2.getX() < 0 && Math.abs((int)(e1.getX() - e2.getX())) > Math.abs((int)(e1.getY() - e2.getY()))
&& Math.abs(velocityX) > minVelocity) {
return mListener.singleFingerSlipAction(GestureEvent.SINGLE_GINGER_RIGHT_SLIP, e1, e2, Math.abs(velocityX));
}
if (e1.getY() - e2.getY() > 0 && Math.abs((int)(e1.getY() - e2.getY())) > Math.abs((int)(e1.getX() - e2.getX()))
&& Math.abs(velocityY) > minVelocity) {
return mListener.singleFingerSlipAction(GestureEvent.SINGLE_GINGER_UP_SLIP, e1, e2, Math.abs(velocityY));
}
if (e1.getY() - e2.getY() < 0 && Math.abs((int)(e1.getY() - e2.getY())) > Math.abs((int)(e1.getX() - e2.getX()))
&& Math.abs(velocityY) > minVelocity) {
return mListener.singleFingerSlipAction(GestureEvent.SINGLE_GINGER_DOWN_SLIP, e1, e2, Math.abs(velocityY));
}
return false;
}
@Override
public boolean onDown(MotionEvent e) {
mListener.onDown(e);
return super.onDown(e);
}
}
private class AiwaysDoubleTapListener implements GestureDetector.OnDoubleTapListener {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return mListener.onSingleTapUp(e);
}
@Override
public boolean onDoubleTap(MotionEvent e) {
return mListener.onDoubleTap(e);
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
return true;
}
}
boolean notifyMutiFingerSlipProcess(MotionEvent startEvent, MotionEvent endEvent, float dX, float dY) {
int pCount = startEvent.getPointerCount();
int sumX = 0, sumY = 0;
for (int i = 0; i < pCount; i++) {
sumX += startEvent.getX(i);
sumY += startEvent.getY(i);
}
int startX = sumX / pCount;
int startY = sumY / pCount;
sumX = sumY =0;
pCount = endEvent.getPointerCount();
for (int i = 0; i < pCount; i++) {
sumX += endEvent.getX(i);
sumY += endEvent.getY(i);
}
int endX = sumX / pCount;
int endY = sumY / pCount;
if (pCount == 2) {
return mListener.mutiFingerSlipProcess(GestureEvent.DOUBLE_GINGER, startX, startY, endX, endY, dX, dY);
}
if (pCount == 3) {
return mListener.mutiFingerSlipProcess(GestureEvent.THREE_GINGER, startX, startY, endX, endY, dX, dY);
}
return false;
}
boolean notifyMutiFingerSlipAction(MotionEvent startEvent, MotionEvent endEvent, float velocityX, float velocityY) {
int pCount = startEvent.getPointerCount();
int sumX = 0, sumY = 0;
for (int i = 0; i < pCount; i++) {
sumX += startEvent.getX(i);
sumY += startEvent.getY(i);
}
int startX = sumX / pCount;
int startY = sumY / pCount;
sumX = sumY = 0;
pCount = endEvent.getPointerCount();
for (int i = 0; i < pCount; i++) {
sumX += endEvent.getX(i);
sumY += endEvent.getY(i);
}
int endX = sumX / pCount;
int endY = sumY / pCount;
Log.d(TAG, "startX:" + startX + " startY:"+startY + " endX: " + endX + " endY: " + endY + " velocityX:" + velocityX + " velocityY: " + velocityY);
if (pCount == 2) {
if (startX - endX > 0 && Math.abs(velocityX) > minVelocity
&& Math.abs(startX - endX) > Math.abs(startY - endY)) {
Log.d(TAG, "DOUBLE_GINGER_LEFT_SLIP");
return mListener.mutiFingerSlipAction(GestureEvent.DOUBLE_GINGER_LEFT_SLIP, startX , startY, endX, endY, velocityX, velocityY);
}
if (startX - endX < 0 && Math.abs(velocityX) > minVelocity
&& Math.abs(startX - endX) > Math.abs(startY - endY)) {
Log.d(TAG, "DOUBLE_GINGER_RIGHT_SLIP");
return mListener.mutiFingerSlipAction(GestureEvent.DOUBLE_GINGER_RIGHT_SLIP, startX , startY, endX, endY, velocityX, velocityY);
}
if (startY - endY > 0 && Math.abs(velocityY) > minVelocity
&& Math.abs(startY - endY) > Math.abs(startX - endX)) {
Log.d(TAG, "DOUBLE_GINGER_UP_SLIP");
return mListener.mutiFingerSlipAction(GestureEvent.DOUBLE_GINGER_UP_SLIP, startX , startY, endX, endY, velocityX, velocityY);
}
if (startY - endY < 0 && Math.abs(velocityY) > minVelocity
&& Math.abs(startY - endY) > Math.abs(startX - endX)) {
Log.d(TAG, "DOUBLE_GINGER_DOWN_SLIP");
return mListener.mutiFingerSlipAction(GestureEvent.DOUBLE_GINGER_DOWN_SLIP, startX , startY, endX, endY, velocityX, velocityY);
}
}
if (pCount == 3) {
if (startY - endY > 0 && Math.abs(velocityY) > minVelocity
&& Math.abs(startY - endY) > Math.abs(startX - endX)) {
Log.d(TAG, "THREE_GINGER_UP_SLIP");
return mListener.mutiFingerSlipAction(GestureEvent.THREE_GINGER_UP_SLIP, startX , startY, endX, endY, velocityX, velocityY);
}
if (startY - endY < 0 && Math.abs(velocityY) > minVelocity
&& Math.abs(startY - endY) > Math.abs(startX - endX)) {
Log.d(TAG, "THREE_GINGER_DOWN_SLIP");
return mListener.mutiFingerSlipAction(GestureEvent.THREE_GINGER_DOWN_SLIP, startX , startY, endX, endY, velocityX, velocityY);
}
}
return false;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
Log.d(TAG, "onScaleBegin");
return mListener.onScaleBegin(detector);
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
Log.d(TAG, "onScaling");
return mListener.onScale(detector);
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
Log.d(TAG, "onScaleEnd");
mListener.onScaleEnd(detector);
}
}
private class AiwaysMultiFingerSplit {
private MotionEvent mStartMutiEvent;
private MotionEvent mLastEvent;
private float mLastFocusX;
private float mLastFocusY;
private float mDownFocusX;
private float mDownFocusY;
public boolean onTouchEvent(MotionEvent ev) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
boolean handled = false;
float sumX = 0, sumY = 0;
float focusX = 0, focusY = 0;
int pCount = ev.getPointerCount();
if (pCount == 2 || pCount == 3) {
for (int i = 0; i < pCount; i++) {
sumX += ev.getX(i);
sumY += ev.getY(i);
}
focusX = sumX / pCount;
focusY = sumY / pCount;
}
switch(ev.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
break;
case MotionEvent.ACTION_UP:
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
if (mLastEvent != null) {
mLastEvent = null;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
if (pCount == 2 || pCount == 3) {
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
mStartMutiEvent = MotionEvent.obtain(ev);
mLastEvent = MotionEvent.obtain(ev);
}
handled = true;
break;
case MotionEvent.ACTION_POINTER_UP:
Log.d(TAG, "ACTION_POINTER_UP_"+ev.getActionIndex() + " ,onTouchEvent pCount:" + ev.getPointerCount());
if ((pCount-1) == 2 || (pCount-1) == 3) {
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
mStartMutiEvent = MotionEvent.obtain(ev);
}
if (pCount == 2 || pCount == 3) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
final int id1 = ev.getPointerId(ev.getActionIndex());
float sumVX = velocityTracker.getXVelocity(id1);
float sumVY = velocityTracker.getYVelocity(id1);
for (int i = 0; i < pCount; i++) {
if (i == ev.getActionIndex()) continue;
final int id2 = ev.getPointerId(i);
sumVX += velocityTracker.getXVelocity(id2);
sumVY += velocityTracker.getYVelocity(id2);
}
final float velocityX = sumVX / pCount;
final float velocityY = sumVY / pCount;
velocityTracker.clear();
handled |= notifyMutiFingerSlipAction(mLastEvent, ev, velocityX , velocityY);
}
break;
case MotionEvent.ACTION_MOVE:
if (pCount == 2 || pCount == 3) {
final float scrollX = mLastFocusX - focusX;
final float scrollY = mLastFocusY - focusY;
if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
handled |= notifyMutiFingerSlipProcess(mStartMutiEvent, ev, scrollX, scrollY);
}
mLastFocusX = focusX;
mLastFocusY = focusY;
handled = true;
}
break;
default:
break;
}
return handled;
}
}
}