標籤:android 監聽雙擊事件 源碼解讀
對於android的雙擊事件的判斷,官方是已經給出解決辦法的,主要是使用下面幾個類或者介面:GestureDetector,OnGestureListener,OnDoubleTapListener,GestureDetector.SimpleOnGestureListener
對於它們的介紹以及用法很多了,就不說明了,大家可以參考下面的部落格:
http://blog.sina.com.cn/s/blog_77c6324101017hs8.html
需要特殊說明的是OnDoubleTapListener這個介面,GestureDetector有個函數setOnDoubleTapListener來設定OnDoubleTapListener,而不是通過建構函式的方式,但讓了通過建構函式的方式也不是不可以,大家可以參考下面的部落格:
http://www.2cto.com/kf/201211/165457.html
通過上面的學習,相信大家就會對這個幾個類用的很熟練了,但是這個並不是我們這篇部落格的重點。
如果你因為某些原因,或者說,就是不想用上面的方法,非要用MotionEvent來判斷雙擊的時間的話,那也木有辦法!~這個網上也有很多的部落格進行了說明。
但是它們的部落格無論什麼實現,都只是通過時間進行判斷,並且也不會進行範圍的判斷,試想,如果你很快速的點擊螢幕最上面和最下面的兩個點,如果按照網路上大部分判斷時間的做法,那肯定就是雙擊時間,但是這顯然是不合理的。
那麼官方源碼是怎麼判斷的呢?我們一起先來看看,博主參考的是android-17版本的源碼。
.... 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;....很明顯的看出來,它們是通過下面這個方法來判斷是否需要分發雙擊事件的:
isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)
下面的代碼就是調用listener的介面來處理雙擊事件,並且擷取處理結果,接著後面就是事件分發機制的事情了,不再本文討論範圍之內。
handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
那麼,那三個參數是怎麼來的呢?我們一個一個來看。
1.mCurrentDownEvent
<span style="color:#333333;">....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; </span><span style="color:#ff6666;">if (mCurrentDownEvent != null) { mCurrentDownEvent.recycle(); } mCurrentDownEvent = MotionEvent.obtain(ev);</span><span style="color:#333333;">....</span>
紅色的字型表明了,是上一次觸發DOWN事件的MotionEvent。
2.mPriviousUpEvent
....case MotionEvent.ACTION_UP: mStillDown = false; <span style="color:#ff6666;">MotionEvent currentUpEvent = MotionEvent.obtain(ev);</span> 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) { handled = mListener.onSingleTapUp(ev); if (mDeferConfirmSingleTap && mDoubleTapListener != null) { mDoubleTapListener.onSingleTapConfirmed(ev); } } else { // 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); } } <span style="color:#ff6666;">if (mPreviousUpEvent != null) { mPreviousUpEvent.recycle(); }</span> // Hold the event we obtained above - listeners may have changed the original. <span style="color:#ff6666;">mPreviousUpEvent = currentUpEvent;</span>....
可以看出來,是上一次觸發UP事件的MotionEvent。
3.ev
....public boolean onTouchEvent(MotionEvent <span style="color:#ff6666;">ev</span>) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(ev, 0); } final int action = ev.getAction();....就是當前發生的MotionEvent的事件。
上述三個參數都找到了,接下來就是看看isConsideredDoubleTap方法裡面做了什麼。
private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp, MotionEvent secondDown) { if (!mAlwaysInBiggerTapRegion) { return false; } if (secondDown.getEventTime() - firstUp.getEventTime() > DOUBLE_TAP_TIMEOUT) { return false; } int deltaX = (int) firstDown.getX() - (int) secondDown.getX(); int deltaY = (int) firstDown.getY() - (int) secondDown.getY(); return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare); }方法很簡單,先判斷了事件,再判斷了範圍。下面看看相關參數的擷取,如下:
final ViewConfiguration configuration = ViewConfiguration.get(context);
private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
doubleTapSlop = configuration.getScaledDoubleTapSlop();
mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
經過上述的觀察,相信大家已經知道了,andorid自己是怎麼判斷的了吧?相應的,我們可以模仿來寫一寫。
首先初始化參數:
ViewConfiguration configuration = ViewConfiguration.get(this);
doubleTapSlop = configuration.getScaledDoubleTapSlop();mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
然後擷取三個參數:
@Overridepublic boolean onTouch(View v, MotionEvent event) {switch(event.getAction()){case MotionEvent.ACTION_DOWN:if(isConsideredDoubleTap(firstDown,firstUp,event)){hideOrShowTitleBar(menuRl.getVisibility() != View.GONE);}if (firstDown != null) {firstDown.recycle(); }firstDown = MotionEvent.obtain(event);hideOrShowTitleBar(menuRl.getVisibility() != View.GONE);break;case MotionEvent.ACTION_UP:if (firstDown != null) {firstUp.recycle(); }firstUp = MotionEvent.obtain(event);break;}return false;}
最後進行判斷:
/** * 一個方法用來判斷雙擊時間 * @param firstDown * @param firstUp * @param secondDown * @return */private boolean isConsideredDoubleTap(MotionEvent firstDowns,MotionEvent firstUps,MotionEvent secondDowns) {if(firstDowns == null || secondDowns == null){return false;}//System.out.println("secondDowns.getEventTime():"+secondDowns.getEventTime());//System.out.println("firstUps.getEventTime():"+firstUps.getEventTime()); if (secondDowns.getEventTime() - firstUps.getEventTime() > Constans.DOUBLE_TAP_TIMEOUT) { return false; } int deltaX = (int) firstDowns.getX() - (int) secondDowns.getX(); int deltaY = (int) firstDowns.getY() - (int) secondDowns.getY();// System.out.println("deltaX:"+deltaX);// System.out.println("deltaY:"+deltaY);// System.out.println("deltaX * deltaX + deltaY * deltaY:"+deltaY);// System.out.println("mDoubleTapSlopSquare:"+mDoubleTapSlopSquare); return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare); }
ok,這樣就大功告成了,是不是比以前用的好多了呢?~!.~
android判斷雙擊事件(參考android源碼,判斷時間間隔和範圍)