android 手势识别 (缩放 单指滑动 多指滑动)

Android P 手势识别

  • 1、前提介绍:
  • 2、单指相关
  • 2、双指缩放
  • 3、多指滑动。
  • 4、总体识别代码

1、前提介绍:

关于Android 手势识别就是当前view 根据用户的不同touch行为,给出不同的处理结果。这里我介绍一下我自己做的一些手势识别如下。
android 手势识别 (缩放 单指滑动 多指滑动)_第1张图片

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:
 * 
    *
  • Create an instance of the {@code GestureDetector} for your {@link View} *
  • 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. *
  • If listening for {@link OnContextClickListener#onContextClick(MotionEvent)} * you must call {@link #onGenericMotionEvent(MotionEvent)} * in {@link View#onGenericMotionEvent(MotionEvent)}. *
*/
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. *

* 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:
 * 
    *
  • Create an instance of the {@code ScaleGestureDetector} for your * {@link View} *
  • 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. *
*/
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: *
    *
  • One {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} *
  • Zero or more {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} *
  • One {@link OnScaleGestureListener#onScaleEnd(ScaleGestureDetector)} *
*/
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. * *

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); } 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;
        }
    }
}

你可能感兴趣的:(android,手势识别)