出處:
炎之鎧郵箱:yanzhikai_yjk@qq.com
博客地址:http://blog.csdn.net/totond
本文原創(chuàng),轉(zhuǎn)載請(qǐng)注明本出處!
前言
在很多視頻播放器中,都存在使用不同的手勢(shì)來控制進(jìn)度、亮度\音量和暫停播放等功能。Android提供了一個(gè)GestureDetector來幫助我們識(shí)別一些基本的觸摸手勢(shì)(還有ScaleGestureDetector可以識(shí)別縮放手勢(shì)),讓我們很方便地實(shí)現(xiàn)手勢(shì)控制功能。下面我們就來學(xué)習(xí)一下GestureDetector的使用和通過源碼(Android7.0)來分析一下它的實(shí)現(xiàn),讓我們對(duì)觸摸事件處理的理解更加深入。
GestureDetector介紹
Detector的意思就是探測(cè)者,所以GestureDetector就是用來監(jiān)聽手勢(shì)的發(fā)生。它內(nèi)部有3個(gè)Listener接口,用來回調(diào)不同類型的觸摸事件,用一個(gè)簡(jiǎn)略的類圖來顯示:
里面這些接口的方法,就是相應(yīng)觸摸事件的回調(diào),實(shí)現(xiàn)了這些方法,就能實(shí)現(xiàn)傳入觸摸事件之后做出相應(yīng)的回調(diào)。
一些回調(diào)接口:
1.OnGestureListener,這個(gè)Listener監(jiān)聽一些手勢(shì),如單擊、滑動(dòng)、長(zhǎng)按等操作:
- onDown(MotionEvent e):用戶按下屏幕的時(shí)候的回調(diào)。
- onShowPress(MotionEvent e):用戶按下按鍵后100ms(根據(jù)Android7.0源碼)還沒有松開或者移動(dòng)就會(huì)回調(diào),官方在源碼的解釋是說一般用于告訴用戶已經(jīng)識(shí)別按下事件的回調(diào)(我暫時(shí)想不出有什么用途,因?yàn)檫@個(gè)回調(diào)觸發(fā)之后還會(huì)觸發(fā)其他的,不像長(zhǎng)按)。
- onLongPress(MotionEvent e):用戶長(zhǎng)按后(好像不同手機(jī)的時(shí)間不同,源碼里默認(rèn)是100ms+500ms)觸發(fā),觸發(fā)之后不會(huì)觸發(fā)其他回調(diào),直至松開(UP事件)。
- onScroll(MotionEvent e1, MotionEvent e2,float distanceX, float distanceY):手指滑動(dòng)的時(shí)候執(zhí)行的回調(diào)(接收到MOVE事件,且位移大于一定距離),e1,e2分別是之前DOWN事件和當(dāng)前的MOVE事件,distanceX和distanceY就是當(dāng)前MOVE事件和上一個(gè)MOVE事件的位移量。
- onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY):用戶執(zhí)行拋操作之后的回調(diào),MOVE事件之后手松開(UP事件)那一瞬間的x或者y方向速度,如果達(dá)到一定數(shù)值(源碼默認(rèn)是每秒50px),就是拋操作(也就是快速滑動(dòng)的時(shí)候松手會(huì)有這個(gè)回調(diào),因此基本上有onFling必然有onScroll)。
-
onSingleTapUp(MotionEvent e):用戶手指松開(UP事件)的時(shí)候如果沒有執(zhí)行
onScroll()
和onLongPress()
這兩個(gè)回調(diào)的話,就會(huì)回調(diào)這個(gè),說明這是一個(gè)點(diǎn)擊抬起事件,但是不能區(qū)分是否雙擊事件的抬起。
2.OnDoubleTapListener,這個(gè)Listener監(jiān)聽雙擊和單擊事件。
- onSingleTapConfirmed(MotionEvent e):可以確認(rèn)(通過單擊DOWN后300ms沒有下一個(gè)DOWN事件確認(rèn))這不是一個(gè)雙擊事件,而是一個(gè)單擊事件的時(shí)候會(huì)回調(diào)。
- onDoubleTap(MotionEvent e):可以確認(rèn)這是一個(gè)雙擊事件的時(shí)候回調(diào)。
-
onDoubleTapEvent(MotionEvent e):
onDoubleTap()
回調(diào)之后的輸入事件(DOWN、MOVE、UP)都會(huì)回調(diào)這個(gè)方法(這個(gè)方法可以實(shí)現(xiàn)一些雙擊后的控制,如讓View雙擊后變得可拖動(dòng)等)。
3.OnContextClickListener,很多人都不知道ContextClick是什么,我以前也不知道,直到我把平板接上了外接鍵盤——原來這就是鼠標(biāo)右鍵。。。
- onContextClick(MotionEvent e):當(dāng)鼠標(biāo)/觸摸板,右鍵點(diǎn)擊時(shí)候的回調(diào)。
4.SimpleOnGestureListener,實(shí)現(xiàn)了上面三個(gè)接口的類,擁有上面三個(gè)的所有回調(diào)方法。
- 由于SimpleOnGestureListener不是抽象類,所以繼承它的時(shí)候只需要選取我們所需要的回調(diào)方法來重寫就可以了,非常方便,也減少了代碼量,符合接口隔離原則,也是模板方法模式的實(shí)現(xiàn)。而實(shí)現(xiàn)上面的三個(gè)接口中的一個(gè)都要全部重寫里面的方法,所以我們一般都是選擇SimpleOnGestureListener。
ps:上面所有的回調(diào)方法的返回值都是boolean類型,和View的事件傳遞機(jī)制一樣,返回true表示消耗了事件,flase表示沒有消耗。
GestureDetector的使用
GestureDetector的使用很簡(jiǎn)單,因?yàn)樗墓δ芫褪嵌x為識(shí)別手勢(shì),所以使用的話就是輸入完整的觸摸事件(完整的意思就是用戶所有的觸摸操作都是輸入給它。為什么要強(qiáng)調(diào)完整,因?yàn)槲?a href="http://www.lxweimin.com/p/9f98dda95ce7" target="_blank">上一篇博客就是分享如何攔截子View的部分觸摸事件),識(shí)別然后進(jìn)行相應(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)聽鼠標(biāo)右鍵點(diǎn)擊事件
return mGestureDetector.onGenericMotionEvent(event);
}
});
}
如上面的代碼,要使用OnGestureListener和OnDoubleTapListener里面的回調(diào)需要調(diào)用
GestureDetector.onTouchEvent()
方法,而使用OnContextClickListener的話則是需要調(diào)用onGenericMotionEvent()
方法,注意一個(gè)是在onTouch()
方法一個(gè)是在onGenericMotion()
方法。
看完了上面一堆文字,其實(shí)你就會(huì)懂得如何使用GestureDetector了,但是如果你想了解它的回調(diào)的時(shí)機(jī)為什么會(huì)是這樣的,想具體了解它們的回調(diào)時(shí)機(jī),可以繼續(xù)看下去,下面是源碼分析。
GestureDetector源碼分析
1. 初始化處理
GestureDetector的源碼接近800行,這在Android源碼中已經(jīng)算是比較短的了(畢竟注釋也占一兩百行了),所以說它的實(shí)現(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) {
//相當(dāng)于下面的getScaledTouchSlop,表示滑動(dòng)的時(shí)候,手的移動(dòng)要大于這個(gè)距離才開始移動(dòng)控件
touchSlop = ViewConfiguration.getTouchSlop();
//相當(dāng)于下面的getScaledDoubleTapTouchSlop,表示點(diǎn)擊的時(shí)候,手指移動(dòng)大于這個(gè)距離,就被認(rèn)為不可能是雙擊
doubleTapTouchSlop = touchSlop;
//相當(dāng)于下面的getScaledDoubleTapSlop,表示第二次點(diǎn)擊的時(shí)候,和第一次的點(diǎn)擊點(diǎn)位置距離如果大于這個(gè),就被認(rèn)為不是雙擊
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();
}
//做平方好計(jì)算距離,后面的距離對(duì)比也是用平方
mTouchSlopSquare = touchSlop * touchSlop;
mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop;
mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
}
可見GestureDetector的創(chuàng)建就是初始化一些屬性,然后就是把對(duì)應(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í)候還沒松開,就設(shè)置mDeferConfirmSingleTap為true,在UP事件的時(shí)候調(diào)用SingleTapConfirme
mDeferConfirmSingleTap = true;
}
}
break;
default:
throw new RuntimeException("Unknown message " + msg); //never
}
}
}
//長(zhǎng)按處理
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();
//開始速度檢測(cè)
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
//檢測(cè)是否非主要指針抬起動(dòng)作(如果是多點(diǎn)觸摸)
final boolean pointerUp =
(action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;
final int skipIndex = pointerUp ? ev.getActionIndex() : -1;
// Determine focal point
// 是非主要指針抬起動(dòng)作的話會(huì)跳過
float sumX = 0, sumY = 0;
final int count = ev.getPointerCount();
//把所有還在觸摸的手指的位置x,y加起來,后面求平均數(shù),算出中心焦點(diǎn)
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;
}
//對(duì)未被處理的事件進(jìn)行一次一致性檢測(cè)
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
}
return handled;
}
上面的注釋寫得很清楚了,主要onTouchEvent()
的主要思路就是先對(duì)輸入事件做出統(tǒng)一處理,提取一些共有的信息,如多個(gè)點(diǎn)同時(shí)觸摸時(shí)候的中心焦點(diǎn)和滑動(dòng)速度等,然后根據(jù)事件的分類做出相應(yīng)的處理。
ps:InputEventConsistencyVerifier對(duì)輸入事件進(jìn)行的一致性檢測(cè)的結(jié)果并不影響GestureDetector的運(yùn)行,如果檢測(cè)到一致性不符合的事件(只有UP事件而前面沒有DOWN事件),就只會(huì)輸出log告訴開發(fā)者。
2.1. DOWN事件處理
下面進(jìn)入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
//延時(shí)發(fā)出單擊事件,如果到了時(shí)間(300ms)還沒有取消的話就確認(rèn)是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;
//處理長(zhǎng)按
if (mIsLongpressEnabled) {
mHandler.removeMessages(LONG_PRESS);
//延時(shí)發(fā)送長(zhǎng)按事件
mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
+ TAP_TIMEOUT + LONGPRESS_TIMEOUT);
}
//延時(shí)發(fā)送showPress事件
mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
handled |= mListener.onDown(ev);
break;
//...
//判斷第二次點(diǎn)擊是否有效雙擊
private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
MotionEvent secondDown) {
//第一次點(diǎn)擊后是否有移動(dòng)超出范圍
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();
//判斷第二次點(diǎn)擊是否在附近,在附近才被認(rèn)為是雙擊
return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);
}
可見,對(duì)DOWN事件涉及:
- 處理單擊判斷:如果收到一次DOWN事件,而且前段時(shí)間沒有DOWN事件的話,會(huì)發(fā)送一個(gè)延時(shí)的TAP信息,而一段時(shí)間(300ms)之后沒有被取消的話,就執(zhí)行GestureHandler里面的TAP單擊確認(rèn)操作。
- 處理雙擊判斷:如果前面也有一次DOWN事件,而且也符合
isConsideredDoubleTap()
的條件(第一次點(diǎn)擊后沒有移動(dòng)超出范圍,第二次點(diǎn)擊也在附近),就可以確認(rèn)雙擊,執(zhí)行onDoubleTap()
和onDoubleTapEvent()
的回調(diào)。 - 處理長(zhǎng)按判斷:先看用戶是否允許檢測(cè)長(zhǎng)按,然后就是發(fā)送一個(gè)延時(shí)的LONG_PRESS信息,如果到時(shí)候還沒被取消的話就是回調(diào)長(zhǎng)按方法了。
- 處理showPress判斷:這個(gè)和長(zhǎng)按差不多,就是時(shí)間(100ms)短了一點(diǎn)而已。
PS:handled是boolean變量,
|=
符號(hào)是用符號(hào)右邊的值跟左邊的值進(jìn)行或運(yùn)算再賦值給左邊。a |= b
等價(jià)于a = a | b
,這里使handled變量初始值為false,進(jìn)行了多次|=
操作,一旦有結(jié)果是true的話,handled最后的值就是true,所有結(jié)果都是false最后才會(huì)false,onTouchEvent()
方法最后返回這個(gè)handled,就可以表示事件有沒被處理,實(shí)現(xiàn)了對(duì)事件處理的封裝。
2.2. MOVE事件處理
然后再看看對(duì)MOVE事件的處理:
//...
case MotionEvent.ACTION_MOVE:
//如果是正在長(zhǎng)按和點(diǎn)擊了鼠標(biāo)右鍵
if (mInLongPress || mInContextClick) {
break;
}
final float scrollX = mLastFocusX - focusX;
final float scrollY = mLastFocusY - focusY;
if (mIsDoubleTapping) {
// Give the move events of the double-tap
//如果是第二次點(diǎn)擊的話,把移動(dòng)事件也當(dāng)作雙擊,有點(diǎn)奇怪
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else if (mAlwaysInTapRegion) {
//down才會(huì)使mAlwaysInTapRegion為true
final int deltaX = (int) (focusX - mDownFocusX);
final int deltaY = (int) (focusY - mDownFocusY);
int distance = (deltaX * deltaX) + (deltaY * deltaY);
//mTouchSlopSquare是一個(gè)距離的平方,表示滑動(dòng)的時(shí)候,手的移動(dòng)要大于這個(gè)距離才認(rèn)為是Scroll事件
if (distance > mTouchSlopSquare) {
//進(jìn)入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) {
//如果移動(dòng)距離超過允許范圍,則不再可能認(rèn)為移動(dòng)事件是雙擊
mAlwaysInBiggerTapRegion = false;
}
} else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
//后續(xù)的Scroll移動(dòng),前面的是進(jìn)入Scroll移動(dòng)
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastFocusX = focusX;
mLastFocusY = focusY;
}
break;
//...
可見,對(duì)MOVE事件涉及:
-
onDoubleTapEvent()
回調(diào):只要確認(rèn)是雙擊之后,mIsDoubleTapping
為true,除了長(zhǎng)按,后面的MOVE事件都會(huì)只回調(diào)onDoubleTapEvent()
。 -
onScroll()
回調(diào):當(dāng)MOVE不是長(zhǎng)按,不是DoubleTapEvent之后,當(dāng)移動(dòng)距離大于一定距離之后,就會(huì)進(jìn)入Scroll模式,然后兩個(gè)MOVE事件的位移距離scrollX或者scrollY大于1px,都會(huì)調(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) {
//長(zhǎng)按結(jié)束
mHandler.removeMessages(TAP);
mInLongPress = false;
} else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
handled = mListener.onSingleTapUp(ev);
//處理單擊確認(rèn),具體邏輯看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;
//...
可見,對(duì)MOVE事件涉及:
-
onDoubleTapEvent()
回調(diào):只要確認(rèn)是雙擊之后,mIsDoubleTapping
為true,除了長(zhǎng)按,后面的MOVE事件都會(huì)只回調(diào)onDoubleTapEvent()
。 -
onSingleTapUp()
回調(diào):DOWN事件之后沒有MOVE,或者M(jìn)OVE的距離沒有超出范圍,mAlwaysInTapRegion
才不會(huì)變成false,回調(diào)onSingleTapUp()
。 -
onSingleTapConfirmed()
回調(diào):從前面GestureHandler里面的TAP消息的實(shí)現(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í)候還沒松開,就設(shè)置mDeferConfirmSingleTap為true,在UP事件的時(shí)候調(diào)用SingleTapConfirme
mDeferConfirmSingleTap = true;
}
}
break;
之前看過,TAP消息是延時(shí)(300ms)發(fā)送的,然而實(shí)際邏輯中,是抬起手指才算是點(diǎn)擊,所以這里處理TAP的時(shí)候就不一定立刻調(diào)用onSingleTapConfirmed()
,而是判斷手指是否松開了,是松開的話就立刻回調(diào)。如果還未松開那就把標(biāo)志位mDeferConfirmSingleTap
設(shè)置為true,等到收到UP事件的時(shí)候再回調(diào)。
-
onFling()
回調(diào):當(dāng)UP事件的速度大于一定速度時(shí),就會(huì)回調(diào)onFling()
,至于mIgnoreNextUpEvent
參數(shù),是只有鼠標(biāo)右鍵點(diǎn)擊的時(shí)候才會(huì)為true,具體看后面。
2.4. 多點(diǎn)觸摸的處理
對(duì)于多個(gè)手指落下,前面的統(tǒng)一處理已經(jīng)是把所有手指的坐標(biāo)值加起來然后算平均值了,所以我們多根手指觸摸的時(shí)候,其實(shí)滑動(dòng)的實(shí)際點(diǎn)是這些手指的中心焦點(diǎn)。回看上面的MOVE事件處理,中心焦點(diǎn)的位置值FocusX和FocusY決定onScroll(onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY))
的后兩個(gè)參數(shù)值,所以處理多點(diǎn)觸控的話就使用它們比較方便。因?yàn)镸otionEvent是使用數(shù)組裝著當(dāng)前屏幕上所有指針的動(dòng)作的,使用前兩個(gè)參數(shù)的話還要循環(huán)用getX(int pointerIndex)
和getY(int pointerIndex)
方法取出各個(gè)指針的值再自己處理。
//...
case MotionEvent.ACTION_POINTER_DOWN:
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
// Cancel long press and taps
//如果有多根手指按下,取消長(zhǎng)按和點(diǎn)擊計(jì)時(shí)
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.
//計(jì)算每一秒鐘的滑動(dòng)像素
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;
}
這是對(duì)多個(gè)手指的UP和DOWN事件處理,其實(shí)就是做一些取消操作而讓多點(diǎn)觸摸不影響單點(diǎn)觸摸的應(yīng)用,例如在多個(gè)手指落下的時(shí)候取消點(diǎn)擊信息等。
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:
//按下觸控筆首選按鈕或者鼠標(biāo)右鍵
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;
//無視下一個(gè)UP事件,因?yàn)樗怯墒髽?biāo)右鍵或者觸控筆鍵帶起的
mIgnoreNextUpEvent = true;
}
break;
}
return false;
}
由此可以,當(dāng)按鍵按下(ACTION_BUTTON_PRESS)的時(shí)候已經(jīng)回調(diào)onContextClick()
。
總結(jié)
讀完源碼,總結(jié)出來的每個(gè)回調(diào)的調(diào)用時(shí)機(jī)如下表:
PS:除去
onContextClick()
,因?yàn)樗陌聪率髽?biāo)右鍵時(shí)候是發(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能識(shí)別很多手勢(shì),但是也是不能滿足所有的需求的,如滑動(dòng)和長(zhǎng)按之后松開沒有回調(diào)(這個(gè)可以重寫onTouch()
捕捉UP事件實(shí)現(xiàn))、多點(diǎn)觸控縮放手勢(shì)的實(shí)現(xiàn)(這個(gè)可以用ScaleGestureDetector)等。
后話
有人問我看GestureDetector源碼這么仔細(xì)有什么用,它又不是很常用的東西,網(wǎng)上隨便一搜一堆資料。我的回答是因?yàn)槲矣X得要用一個(gè)東西的話,首先就是要搞清楚它能干什么,它的限制是什么,為什么要選擇它,關(guān)于這些方面,網(wǎng)上的很多關(guān)于GestureDetector的資料都沒有達(dá)到我想了解的程度,加上GestureDetector并不復(fù)雜,所以寫下了這篇博客,這樣就可以從源碼層面上了解到它的回調(diào)是什么時(shí)候調(diào)用,有bug的時(shí)候也能更快的找出。
不管怎樣,GestureDetector里面的SimpleOnGestureListener的設(shè)計(jì),和對(duì)觸摸事件的處理方式是很值得我學(xué)習(xí)的,記錄分享至此,水平有限,如果錯(cuò)漏,歡迎指正和討論。