安卓源碼給我們提供了GestureDetector來監聽手勢,點擊,長按,雙擊,滾動,拋等事件;通過解讀Gesture類來看看安卓源碼是怎樣判斷各類手勢的?搞清楚了這個,我們就可以自定義手勢啦,比如一張圖片,點擊返回,長按保存,單個手指雙擊 圖片放大到2倍或者由放大狀態變回原來的大小,兩個手指捏 縮小圖片,兩個手指張開 放大圖片等等。
先看總結分析:
1.看源碼之前,我們先來看一些變量。
2.GestureDetector
類提供了兩個接口:
OnGestureListener :
用來監聽單個手指事件OnDoubleTapListener :
用來監聽兩個手指事件- 內部類
SimpleOnGestureListener
實現了上面2個接口:
3.消息類GestureHandler :
消息類型有3種:TAP,SHOW_PRESS,LONG_PRESS
- SHOW_PRESS:回調onShowPress()接口
- LONG_PRESS:回調onLongPress()接口,即長按事件。
- TAP:如果當前手指不是down狀態,回調onSingleTapConfirmed()接口,即單擊事件。
4.單擊,長按,雙擊,移動等這些事件的判斷條件
源碼是怎麼判斷單擊,長按,雙擊,移動等這些事件的呢?當手指處於按下狀態,即down事件,會發送一些消息TAP,LONG_PRESS,SHOW_PRESS(延時發送或在指定的時間點發送),然後在接下來的事件中分別去判斷,如果是移動事件,移除TAP,LONG_PRESS和SHOW_PRESS消息;如果是長按事件,就移除TAP消息。這樣,如果沒有移除TAP消息,那麼handler中會回調相應接口。一般雙擊的判斷在down事件,移動的判斷髮生在move事件,而雙擊的消費發生在up事件,回調onShowPress()
,長按回調onLongPress()
,單擊回調onSingleTapConfirmed()
都發生在handleMessage
中。
各種手勢調用方法如下:
- 快速點擊(沒有滑動):onDown() -> onSingleTapUp() -> onSingleTapConfirmed()
- 手指一直按下,即長按(沒有滑動,沒有擡起):onDown() -> onShowPress() -> onLongPress()
- 滾動:onDown() -> onScroll() -> onScroll() ... ...
- 拋:onDown() -> onScroll() -> ... ... -> onFling()
(onTouch()::ACTION_DOWN) -> onDown() -> (onTouch()::ACTION_MOVE) -> onScroll() -> ... ...(onTouch()::ACTION_MOVE) ->onScroll() -> (onTouch()::ACTION_UP) -> onFling()
-
單擊
點擊時間在 DOUBLE_TAP_TIMEOUT 內未進行第二次單擊事件。
單擊的回調接口爲onSingleTapConfirmed()
。onSingleTapConfirmed()
的調用有兩個地方:
- handlerMessage中
- up事件中
handlerMessage
中消息類型爲TAP並且只要當前不是按下狀態就會調用,up事件中,首先判斷雙擊和長按是否正在進行,如 果都不是,判斷如果移動區域在mTouchSlopSquare之內並且mDeferConfirmSingleTap
爲true,會調用。
-
長按
- 手指處於按下狀態
- 按下時間未超過LONGPRESS_TIMEOUT。通過
ViewConfiguration.getLongPressTimeout()
來獲取- 移動未超過mTouchSlopSquare的距離。
-
雙擊
是否有上一次的單擊事件(上一次down事件和up事件不爲空)
mAlwaysInBiggerTapRegion爲true。即每次單擊的移動距離在 mDoubleTapTouchSlopSquare 之內。
第二次單擊的down事件和第一次單擊的up事件的時間間隔在 DOUBLE_TAP_MIN_TIME 和 DOUBLE_TAP_TIMEOUT之間。
第一次按下的位置和第二次按下的位置之間的距離小於mDoubleTapSlopSquare。
-
移動
mAlwaysInTapRegion爲false 或者 mAlwaysInTapRegion爲true並且 移動區域在 mTouchSlopSquare 和 mDoubleTapTouchSlopSquare 之間。
5. 各個事件的具體實現和分析
主要看MotionEvent事件:
-
MotionEvent.ACTION_POINTER_DOWN:
-
MotionEvent.ACTION_POINTER_UP:
-
MotionEvent.ACTION_DOWN:
-
MotionEvent.ACTION_MOVE:
-
MotionEvent.ACTION_UP:
/*
* 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.
*/
//手指按下就會觸發(調用onTouch()方法的ACTION_DOWN事件時觸發)
boolean onDown(MotionEvent e);
/**
* 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
*/
/*
*down事件發生而move或則up還沒發生前觸發該
*事件;Touch了還沒有滑動時觸發(與onDown,onLongPress)比較onDown只要Touch down一定立刻觸發。而Touchdown後過一會沒有滑動先觸發onShowPress再是onLongPress。所以Touchdown後一直不滑動
*onLongPress之前觸發
*/
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
*/
//一次點擊up事件;在touch down後又沒有滑動(onScroll),又沒有長按(onLongPress),然後Touchup時觸發
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
*/
//滾動,觸摸屏按下後移動(執行onTouch()方法的ACTION_MOVE事件時會觸發)
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);
//滑動,觸摸屏按下後快速移動並擡起,會先觸發滾動手勢,跟着觸發一個滑動手勢
//在ACTION_UP時纔會觸發
/*參數:
e1:第1個ACTION_DOWN MotionEvent 並且只有一個;
e2:最後一個ACTION_MOVE MotionEvent ;
velocityX:X軸上的移動速度,像素/秒 ;
velocityY:Y軸上的移動速度,像素/秒.
觸發條件:X軸的座標位移大於FLING_MIN_DISTANCE,且移動速度大於FLING_MIN_VELOCITY個像素/秒
*/
boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
}
/**
* 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
*/
/*
*1. 單擊確認,用來判定該次點擊是SingleTap而不是DoubleTap,如果連續點擊兩次就是DoubleTap手勢,如果只點擊一次,系統等待一段時間後沒有收到第二次點擊則判定該次點擊爲SingleTap而不是DoubleTap,然後觸發SingleTapConfirmed事件。
*2. 不同於onSingleTapUp,他是在GestureDetector確信用戶在第一次觸摸屏幕後,沒有緊跟着第二次觸摸屏幕,也就是不是“雙擊”的時候觸發
*/
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);
}
/**
* 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.
*/
//內部類SimpleOnGestureListener,實現了上面2個接口:
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;
}
}
//點擊區域的變量:
// 判斷是否是單擊,是否能構成onScroll事件。移動距離小於mTouchSlopSquare,則是單擊,大於,則是onScroll
private int mTouchSlopSquare;
//判斷是否構成雙擊,單次單擊移動距離小於mDoubleTapTouchSlopSquare,mAlwaysInBiggerTapRegion爲true。
private int mDoubleTapTouchSlopSquare;
//判斷是否構成雙擊,第二次按下的位置和第一次擡起的位置的距離小於mDoubleTapSlopSquare,則構成雙擊
private int mDoubleTapSlopSquare;
private int mMinimumFlingVelocity;
private int mMaximumFlingVelocity;
//時間的變量:
//發送SHOW_PRESS消息的時間,發生在ACTION_MOVE和ACTION_UP之前。如果在ACTION_MOVE中判斷爲onScroll事件,則取消SHOW_PRESS消息。
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();
//手勢類型
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;
//是否按下。ACTION_DOWN爲true,ACTION_UP 和 cancle 爲false
private boolean mStillDown;
//是否延遲確定點擊。確定點擊即消費點擊,其實就是點擊事件。
//在handleMessage中消息類型爲TAP時,如果手指爲非按下狀態,
//則回調onSingleTapConfirmed接口,否則,mDeferConfirmSingleTap置爲true,
//其他情況全部置爲false:ACTION_DOWN,ACTION_UP結尾,處理長按事件,cancle。
private boolean mDeferConfirmSingleTap;
////是否正在處理長按事件
private boolean mInLongPress;
private boolean mInContextClick;
//是否當前手指僅在小範圍內移動,當手指僅在小範圍內移動時,視爲手指未曾移動過,不會觸發onScroll手勢。用來判斷點擊事件和移動事件。移動距離大於mTouchSlopSquare時,爲false。
private boolean mAlwaysInTapRegion;
//是否當前手指在較大範圍內移動,判斷雙擊手勢。此值爲true時,雙擊手勢成立。移動距離大於mDoubleTapTouchSlopSquare時,爲false。
private boolean mAlwaysInBiggerTapRegion;
private boolean mIgnoreNextUpEvent;
private MotionEvent mCurrentDownEvent;//當前down事件
private MotionEvent mPreviousUpEvent;//上一次up事件
/**
* 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;
//上一次焦點的x座標
private float mLastFocusX;
private float mLastFocusY;
//當前焦點的x座標
private float mDownFocusX;
private float mDownFocusY;
//是否允許雙擊事件發生,默認爲true,除非手動去設置。只有爲true,雙擊事件才能進行
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;
//消息類GestureHandler
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://回調onShowPress()接口
mListener.onShowPress(mCurrentDownEvent);
break;
case LONG_PRESS://回調onLongPress()接口,即長按事件。
dispatchLongPress();////處理長按事件
break;
case TAP://如果當前手指不是down狀態,回調onSingleTapConfirmed()接口,即單擊事件。
// 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);
//如果當前handler有TAP消息,移除所有的TAP消息
if (hadTapMessage) mHandler.removeMessages(TAP);
//判斷是否是雙擊,如果是,設置回調onDoubleTap和onDoubleTapEvent。如果不是,發送一個延時消息TAP,延時爲DOUBLE_TAP_TIMEOUT。
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 {
// 第一次點擊發送
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;
//如果允許長安事件發生,在 mCurrentDownEvent.getDownTime()+TAP_TIMEOUT+LONGPRESS_TIMEOUT 時間發送LONG_PRESS消息。
if (mIsLongpressEnabled) {
mHandler.removeMessages(LONG_PRESS);
mHandler.sendEmptyMessageAtTime(LONG_PRESS,
mCurrentDownEvent.getDownTime() + LONGPRESS_TIMEOUT);
}
//發送SHOW_PRESS消息,在mCurrentDownEvent.getDownTime() + TAP_TIMEOUT 時間。
mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
handled |= mListener.onDown(ev);
break;
case MotionEvent.ACTION_MOVE:
//判斷是否正在進行長按事件,是,break,否,向下執行。
if (mInLongPress || mInContextClick) {
break;
}
final float scrollX = mLastFocusX - focusX;
final float scrollY = mLastFocusY - focusY;
//判斷是否是雙擊事件,是,回調onDoubleTapEvent()接口,否,向下執行。
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;
//判斷是否是scroll事件,是,移除所有消息,並且向下執行。
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);
//如果是雙擊事件,回調onDoubleTapEvent接口
if (mIsDoubleTapping) {
// Finally, give the up event of the double-tap
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
//如果正在執行長按事件,移除TAP消息,mInLongPress置爲false。mInLongPress爲boolean類型的變量,判斷是否正在執行長按事件。
} else if (mInLongPress) {
mHandler.removeMessages(TAP);
mInLongPress = false;
} else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {//如果是移動事件,回調onSingleTapUp接口;
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;
//移除SHOW_PRESS和LONG_PRESS消息。
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);
}
}