出處:
炎之鎧郵箱:yanzhikai_yjk@qq.com
博客地址:http://blog.csdn.net/totond
本文原創(chuàng),轉(zhuǎn)載請注明本出處!
前言
在很多視頻播放器中,都存在使用不同的手勢來控制進度、亮度\音量和暫停播放等功能。Android提供了一個GestureDetector來幫助我們識別一些基本的觸摸手勢(還有ScaleGestureDetector可以識別縮放手勢),讓我們很方便地實現(xiàn)手勢控制功能。下面我們就來學(xué)習(xí)一下GestureDetector的使用和通過源碼(Android7.0)來分析一下它的實現(xiàn),讓我們對觸摸事件處理的理解更加深入。
GestureDetector介紹
Detector的意思就是探測者,所以GestureDetector就是用來監(jiān)聽手勢的發(fā)生。它內(nèi)部有3個Listener接口,用來回調(diào)不同類型的觸摸事件,用一個簡略的類圖來顯示:

里面這些接口的方法,就是相應(yīng)觸摸事件的回調(diào),實現(xiàn)了這些方法,就能實現(xiàn)傳入觸摸事件之后做出相應(yīng)的回調(diào)。
一些回調(diào)接口:
1.OnGestureListener,這個Listener監(jiān)聽一些手勢,如單擊、滑動、長按等操作:
- onDown(MotionEvent e):用戶按下屏幕的時候的回調(diào)。
- onShowPress(MotionEvent e):用戶按下按鍵后100ms(根據(jù)Android7.0源碼)還沒有松開或者移動就會回調(diào),官方在源碼的解釋是說一般用于告訴用戶已經(jīng)識別按下事件的回調(diào)(我暫時想不出有什么用途,因為這個回調(diào)觸發(fā)之后還會觸發(fā)其他的,不像長按)。
- onLongPress(MotionEvent e):用戶長按后(好像不同手機的時間不同,源碼里默認是100ms+500ms)觸發(fā),觸發(fā)之后不會觸發(fā)其他回調(diào),直至松開(UP事件)。
- onScroll(MotionEvent e1, MotionEvent e2,float distanceX, float distanceY):手指滑動的時候執(zhí)行的回調(diào)(接收到MOVE事件,且位移大于一定距離),e1,e2分別是之前DOWN事件和當前的MOVE事件,distanceX和distanceY就是當前MOVE事件和上一個MOVE事件的位移量。
- onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY):用戶執(zhí)行拋操作之后的回調(diào),MOVE事件之后手松開(UP事件)那一瞬間的x或者y方向速度,如果達到一定數(shù)值(源碼默認是每秒50px),就是拋操作(也就是快速滑動的時候松手會有這個回調(diào),因此基本上有onFling必然有onScroll)。
-
onSingleTapUp(MotionEvent e):用戶手指松開(UP事件)的時候如果沒有執(zhí)行
onScroll()和onLongPress()這兩個回調(diào)的話,就會回調(diào)這個,說明這是一個點擊抬起事件,但是不能區(qū)分是否雙擊事件的抬起。
2.OnDoubleTapListener,這個Listener監(jiān)聽雙擊和單擊事件。
- onSingleTapConfirmed(MotionEvent e):可以確認(通過單擊DOWN后300ms沒有下一個DOWN事件確認)這不是一個雙擊事件,而是一個單擊事件的時候會回調(diào)。
- onDoubleTap(MotionEvent e):可以確認這是一個雙擊事件的時候回調(diào)。
-
onDoubleTapEvent(MotionEvent e):
onDoubleTap()回調(diào)之后的輸入事件(DOWN、MOVE、UP)都會回調(diào)這個方法(這個方法可以實現(xiàn)一些雙擊后的控制,如讓View雙擊后變得可拖動等)。
3.OnContextClickListener,很多人都不知道ContextClick是什么,我以前也不知道,直到我把平板接上了外接鍵盤——原來這就是鼠標右鍵。。。
- onContextClick(MotionEvent e):當鼠標/觸摸板,右鍵點擊時候的回調(diào)。
4.SimpleOnGestureListener,實現(xiàn)了上面三個接口的類,擁有上面三個的所有回調(diào)方法。
- 由于SimpleOnGestureListener不是抽象類,所以繼承它的時候只需要選取我們所需要的回調(diào)方法來重寫就可以了,非常方便,也減少了代碼量,符合接口隔離原則,也是模板方法模式的實現(xiàn)。而實現(xiàn)上面的三個接口中的一個都要全部重寫里面的方法,所以我們一般都是選擇SimpleOnGestureListener。
ps:上面所有的回調(diào)方法的返回值都是boolean類型,和View的事件傳遞機制一樣,返回true表示消耗了事件,flase表示沒有消耗。
GestureDetector的使用
GestureDetector的使用很簡單,因為它的功能就是定義為識別手勢,所以使用的話就是輸入完整的觸摸事件(完整的意思就是用戶所有的觸摸操作都是輸入給它。為什么要強調(diào)完整,因為我上一篇博客就是分享如何攔截子View的部分觸摸事件),識別然后進行相應(yīng)的回調(diào):
private void init(Context context){
mOnGestureListener = new MyOnGestureListener();
mGestureDetector = new GestureDetector(context,mOnGestureListener);
// mGestureDetector.setIsLongpressEnabled(false);
setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
//監(jiān)聽觸摸事件
return mGestureDetector.onTouchEvent(event);
}
});
setOnGenericMotionListener(new OnGenericMotionListener() {
@Override
public boolean onGenericMotion(View v, MotionEvent event) {
Log.d(TAG, "onGenericMotion: ");
//監(jiān)聽鼠標右鍵點擊事件
return mGestureDetector.onGenericMotionEvent(event);
}
});
}
如上面的代碼,要使用OnGestureListener和OnDoubleTapListener里面的回調(diào)需要調(diào)用
GestureDetector.onTouchEvent()方法,而使用OnContextClickListener的話則是需要調(diào)用onGenericMotionEvent()方法,注意一個是在onTouch()方法一個是在onGenericMotion()方法。
看完了上面一堆文字,其實你就會懂得如何使用GestureDetector了,但是如果你想了解它的回調(diào)的時機為什么會是這樣的,想具體了解它們的回調(diào)時機,可以繼續(xù)看下去,下面是源碼分析。
GestureDetector源碼分析
1. 初始化處理
GestureDetector的源碼接近800行,這在Android源碼中已經(jīng)算是比較短的了(畢竟注釋也占一兩百行了),所以說它的實現(xiàn)也不是很復(fù)雜的。從它的構(gòu)造方法開始:
public GestureDetector(Context context, OnGestureListener listener) {
this(context, listener, null);
}
public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
//初始化Handler
if (handler != null) {
mHandler = new GestureHandler(handler);
} else {
mHandler = new GestureHandler();
}
//設(shè)置Listener
mListener = listener;
if (listener instanceof OnDoubleTapListener) {
setOnDoubleTapListener((OnDoubleTapListener) listener);
}
if (listener instanceof OnContextClickListener) {
setContextClickListener((OnContextClickListener) listener);
}
init(context);
}
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) {
//相當于下面的getScaledTouchSlop,表示滑動的時候,手的移動要大于這個距離才開始移動控件
touchSlop = ViewConfiguration.getTouchSlop();
//相當于下面的getScaledDoubleTapTouchSlop,表示點擊的時候,手指移動大于這個距離,就被認為不可能是雙擊
doubleTapTouchSlop = touchSlop;
//相當于下面的getScaledDoubleTapSlop,表示第二次點擊的時候,和第一次的點擊點位置距離如果大于這個,就被認為不是雙擊
doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
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;
}
可見GestureDetector的創(chuàng)建就是初始化一些屬性,然后就是把對應(yīng)的Listener設(shè)置好,還有初始化Handler,而這里的GestureHandler,是控制onShowPress(),onLongPress(),onSingleTapConfirmed()`回調(diào)的關(guān)鍵:
private class GestureHandler extends Handler {
//省略構(gòu)造函數(shù)...
@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
//這里控制SingleTapConfirmed的回調(diào),
if (mDoubleTapListener != null) {
if (!mStillDown) {
//如果已經(jīng)松開,就立刻調(diào)用SingleTapConfirmed
mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
} else {
//如果處理Message的時候還沒松開,就設(shè)置mDeferConfirmSingleTap為true,在UP事件的時候調(diào)用SingleTapConfirme
mDeferConfirmSingleTap = true;
}
}
break;
default:
throw new RuntimeException("Unknown message " + msg); //never
}
}
}
//長按處理
private void dispatchLongPress() {
mHandler.removeMessages(TAP);
mDeferConfirmSingleTap = false;
mInLongPress = true;
mListener.onLongPress(mCurrentDownEvent);
}
2. 輸入處理
初始化完之后,就是看它的如何處理輸入了,這是它的核心邏輯:
public boolean onTouchEvent(MotionEvent ev) {
//檢查事件輸入的一致性,log出來一致性的信息,如:有事件只有up沒有down
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;
// Determine focal point
// 是非主要指針抬起動作的話會跳過
float sumX = 0, sumY = 0;
final int count = ev.getPointerCount();
//把所有還在觸摸的手指的位置x,y加起來,后面求平均數(shù),算出中心焦點
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:
//...
break;
case MotionEvent.ACTION_POINTER_UP:
//...
break;
case MotionEvent.ACTION_DOWN:
//...
break;
case MotionEvent.ACTION_MOVE:
//...
break;
case MotionEvent.ACTION_UP:
//...
break;
case MotionEvent.ACTION_CANCEL:
cancel();
break;
}
//對未被處理的事件進行一次一致性檢測
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
}
return handled;
}
上面的注釋寫得很清楚了,主要onTouchEvent()的主要思路就是先對輸入事件做出統(tǒng)一處理,提取一些共有的信息,如多個點同時觸摸時候的中心焦點和滑動速度等,然后根據(jù)事件的分類做出相應(yīng)的處理。
ps:InputEventConsistencyVerifier對輸入事件進行的一致性檢測的結(jié)果并不影響GestureDetector的運行,如果檢測到一致性不符合的事件(只有UP事件而前面沒有DOWN事件),就只會輸出log告訴開發(fā)者。
2.1. DOWN事件處理
下面進入DOWN事件的處理:
//...
case MotionEvent.ACTION_DOWN:
if (mDoubleTapListener != null) {
//處理雙擊
//取消TAP事件
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
//回調(diào)雙擊
handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
// Give a callback with down event of the double-tap
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else {
// This is a first tap
//延時發(fā)出單擊事件,如果到了時間(300ms)還沒有取消的話就確認是TAP事件了
mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
}
}
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
//重置mCurrentDownEvent
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);
//延時發(fā)送長按事件
mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
+ TAP_TIMEOUT + LONGPRESS_TIMEOUT);
}
//延時發(fā)送showPress事件
mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
handled |= mListener.onDown(ev);
break;
//...
//判斷第二次點擊是否有效雙擊
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();
//判斷第二次點擊是否在附近,在附近才被認為是雙擊
return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);
}
可見,對DOWN事件涉及:
- 處理單擊判斷:如果收到一次DOWN事件,而且前段時間沒有DOWN事件的話,會發(fā)送一個延時的TAP信息,而一段時間(300ms)之后沒有被取消的話,就執(zhí)行GestureHandler里面的TAP單擊確認操作。
- 處理雙擊判斷:如果前面也有一次DOWN事件,而且也符合
isConsideredDoubleTap()的條件(第一次點擊后沒有移動超出范圍,第二次點擊也在附近),就可以確認雙擊,執(zhí)行onDoubleTap()和onDoubleTapEvent()的回調(diào)。 - 處理長按判斷:先看用戶是否允許檢測長按,然后就是發(fā)送一個延時的LONG_PRESS信息,如果到時候還沒被取消的話就是回調(diào)長按方法了。
- 處理showPress判斷:這個和長按差不多,就是時間(100ms)短了一點而已。
PS:handled是boolean變量,
|=符號是用符號右邊的值跟左邊的值進行或運算再賦值給左邊。a |= b等價于a = a | b,這里使handled變量初始值為false,進行了多次|=操作,一旦有結(jié)果是true的話,handled最后的值就是true,所有結(jié)果都是false最后才會false,onTouchEvent()方法最后返回這個handled,就可以表示事件有沒被處理,實現(xiàn)了對事件處理的封裝。
2.2. MOVE事件處理
然后再看看對MOVE事件的處理:
//...
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) {
//down才會使mAlwaysInTapRegion為true
final int deltaX = (int) (focusX - mDownFocusX);
final int deltaY = (int) (focusY - mDownFocusY);
int distance = (deltaX * deltaX) + (deltaY * deltaY);
//mTouchSlopSquare是一個距離的平方,表示滑動的時候,手的移動要大于這個距離才認為是Scroll事件
if (distance > mTouchSlopSquare) {
//進入Scroll模式
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastFocusX = focusX;
mLastFocusY = focusY;
mAlwaysInTapRegion = false;
mHandler.removeMessages(TAP);
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
}
if (distance > mDoubleTapTouchSlopSquare) {
//如果移動距離超過允許范圍,則不再可能認為移動事件是雙擊
mAlwaysInBiggerTapRegion = false;
}
} else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
//后續(xù)的Scroll移動,前面的是進入Scroll移動
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastFocusX = focusX;
mLastFocusY = focusY;
}
break;
//...
可見,對MOVE事件涉及:
-
onDoubleTapEvent()回調(diào):只要確認是雙擊之后,mIsDoubleTapping為true,除了長按,后面的MOVE事件都會只回調(diào)onDoubleTapEvent()。 -
onScroll()回調(diào):當MOVE不是長按,不是DoubleTapEvent之后,當移動距離大于一定距離之后,就會進入Scroll模式,然后兩個MOVE事件的位移距離scrollX或者scrollY大于1px,都會調(diào)用onScroll()。
2.3. UP事件的處理
接下來再看UP事件:
//...
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) {
//長按結(jié)束
mHandler.removeMessages(TAP);
mInLongPress = false;
} else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
handled = mListener.onSingleTapUp(ev);
//處理單擊確認,具體邏輯看GestureHandler如何處理TAP事件
if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
mDoubleTapListener.onSingleTapConfirmed(ev);
}
} else if (!mIgnoreNextUpEvent) {
//處理Fling,如果速度大于定義的最小速度(50),就回調(diào)Fling
// 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);
}
}
//重置mPreviousUpEvent
if (mPreviousUpEvent != null) {
mPreviousUpEvent.recycle();
}
// Hold the event we obtained above - listeners may have changed the original.
mPreviousUpEvent = currentUpEvent;
//回收mVelocityTracker
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;
//...
可見,對MOVE事件涉及:
-
onDoubleTapEvent()回調(diào):只要確認是雙擊之后,mIsDoubleTapping為true,除了長按,后面的MOVE事件都會只回調(diào)onDoubleTapEvent()。 -
onSingleTapUp()回調(diào):DOWN事件之后沒有MOVE,或者MOVE的距離沒有超出范圍,mAlwaysInTapRegion才不會變成false,回調(diào)onSingleTapUp()。 -
onSingleTapConfirmed()回調(diào):從前面GestureHandler里面的TAP消息的實現(xiàn)可以看到:
case TAP:
// If the user's finger is still down, do not count it as a tap
//這里控制SingleTapConfirmed的回調(diào),
if (mDoubleTapListener != null) {
if (!mStillDown) {
//如果已經(jīng)松開,就立刻調(diào)用SingleTapConfirmed
mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
} else {
//如果處理Message的時候還沒松開,就設(shè)置mDeferConfirmSingleTap為true,在UP事件的時候調(diào)用SingleTapConfirme
mDeferConfirmSingleTap = true;
}
}
break;
之前看過,TAP消息是延時(300ms)發(fā)送的,然而實際邏輯中,是抬起手指才算是點擊,所以這里處理TAP的時候就不一定立刻調(diào)用onSingleTapConfirmed(),而是判斷手指是否松開了,是松開的話就立刻回調(diào)。如果還未松開那就把標志位mDeferConfirmSingleTap設(shè)置為true,等到收到UP事件的時候再回調(diào)。
-
onFling()回調(diào):當UP事件的速度大于一定速度時,就會回調(diào)onFling(),至于mIgnoreNextUpEvent參數(shù),是只有鼠標右鍵點擊的時候才會為true,具體看后面。
2.4. 多點觸摸的處理
對于多個手指落下,前面的統(tǒng)一處理已經(jīng)是把所有手指的坐標值加起來然后算平均值了,所以我們多根手指觸摸的時候,其實滑動的實際點是這些手指的中心焦點?;乜瓷厦娴腗OVE事件處理,中心焦點的位置值FocusX和FocusY決定onScroll(onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY))的后兩個參數(shù)值,所以處理多點觸控的話就使用它們比較方便。因為MotionEvent是使用數(shù)組裝著當前屏幕上所有指針的動作的,使用前兩個參數(shù)的話還要循環(huán)用getX(int pointerIndex)和getY(int pointerIndex)方法取出各個指針的值再自己處理。
//...
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);
//如果剩下的手指速度方向是和抬起那根手指的速度相反方向的,就說明不是fling,清空速度監(jiān)聽
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;
//...
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;
}
這是對多個手指的UP和DOWN事件處理,其實就是做一些取消操作而讓多點觸摸不影響單點觸摸的應(yīng)用,例如在多個手指落下的時候取消點擊信息等。
2.5. ContextClick的處理
ContextClick的事件是由onGenericMotionEvent()傳入:
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;
//無視下一個UP事件,因為它是由鼠標右鍵或者觸控筆鍵帶起的
mIgnoreNextUpEvent = true;
}
break;
}
return false;
}
由此可以,當按鍵按下(ACTION_BUTTON_PRESS)的時候已經(jīng)回調(diào)onContextClick()。
總結(jié)
讀完源碼,總結(jié)出來的每個回調(diào)的調(diào)用時機如下表:
PS:除去
onContextClick(),因為它的按下鼠標右鍵時候是發(fā)出一系列的事件。
| 回調(diào)/輸入事件 | DOWN事件 | MOVE事件 | UP事件 |
|---|---|---|---|
| onDown(MotionEvent e) | √ | × | × |
| onShowPress(MotionEvent e) | √ | × | × |
| onLongPress(MotionEvent e) | √ | × | × |
| onScroll(MotionEvent e1, MotionEvent e2,float distanceX, float distanceY) | × | √ | × |
| onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY)) | × | × | √ |
| onSingleTapUp(MotionEvent e) | × | × | √ |
| onSingleTapConfirmed(MotionEvent e) | × | × | √ |
| onDoubleTap(MotionEvent e) | √ | × | × |
| onDoubleTapEvent(MotionEvent e) | √ | √ | √ |
從上面的分析可以看出,雖然GestureDetector能識別很多手勢,但是也是不能滿足所有的需求的,如滑動和長按之后松開沒有回調(diào)(這個可以重寫onTouch()捕捉UP事件實現(xiàn))、多點觸控縮放手勢的實現(xiàn)(這個可以用ScaleGestureDetector)等。
后話
有人問我看GestureDetector源碼這么仔細有什么用,它又不是很常用的東西,網(wǎng)上隨便一搜一堆資料。我的回答是因為我覺得要用一個東西的話,首先就是要搞清楚它能干什么,它的限制是什么,為什么要選擇它,關(guān)于這些方面,網(wǎng)上的很多關(guān)于GestureDetector的資料都沒有達到我想了解的程度,加上GestureDetector并不復(fù)雜,所以寫下了這篇博客,這樣就可以從源碼層面上了解到它的回調(diào)是什么時候調(diào)用,有bug的時候也能更快的找出。
不管怎樣,GestureDetector里面的SimpleOnGestureListener的設(shè)計,和對觸摸事件的處理方式是很值得我學(xué)習(xí)的,記錄分享至此,水平有限,如果錯漏,歡迎指正和討論。