史上最會“抄”的程式設計師!這逆天技能,善於將系統原始碼為己用!
起源於一位同事問我:“怎麼優雅的監聽雙擊”這個行為?
其實很多類似的事件相關的,我們都可以參考系統原始碼,因為有時候完全引入系統能力有些麻煩,我們可能就想順手實現一個功能。
例如上面同事問的:
怎麼優雅的監聽雙擊?
相信大家或多或少都有一些實現方案,不過系統有給我們提供
GestureDetector
類,如果你熟知該類實現,那麼就能選擇出於系統一樣的方案,程式碼的認可度也會提高。
所以今天我們就藉機學習下:
GestureDetector
關於支援的手勢是如何檢測的?
下面內容是小緣分析的。
我們在建立這個類例項的時候,需要把介面
OnGestureListener
(用來監聽各種手勢)實現並作為引數傳進它的構造方法中:
解釋一下各個方法的回撥時機(都非常容易理解):
-
onDown
:手指按下; -
onShowPress
:手指按下後,100毫秒內未抬起、未移動; -
onSingleTapUp
:手指按下後未移動,並在500毫秒內抬起(可以認定為單擊); -
onScroll
:手指拖動; -
onLongPress
:長按(手指按下後,500毫秒內未抬起、未移動); -
onFling
:手指快速拖動後鬆手(慣性滾動);
除了
OnGestureListener
之外,還有一個
OnDoubleTapListener
,看名字就能猜到是用來監聽雙擊事件的了:
解釋一下:
-
onSingleTapConfirmed
:已經確認這是一次單擊事件,想觸發雙擊必須繼續快速點選兩次螢幕(即:手指抬起之後,300毫秒內沒等到手指再次按下); -
onDoubleTap
:觸發雙擊事件(手指抬起後300毫秒內再次按下(注意:是再次按下時就觸發,並不是等它抬起後才觸發)) -
onDoubleTapEvent
:觸發雙擊後的手指觸控事件,包括ACTION_DOWN
、ACTION_MOVE
、ACTION_UP
(注意:在觸發長按後,不會繼續收到ACTION_MOVE
事件,因為在手指長按過程中,是不需要處理手指移動的動作的,也就是會直接忽略ACTION_MOVE
的事件。還有,此方法回撥後,在觸發長按事件之前,如有新手指按下,則不再認定是雙擊了,所以不會繼續回撥此方法,取而代之的是onScroll
)。此方法與上面的onDoubleTap
方法的區別就是,onDoubleTap
在一次雙擊事件中只會回撥一次,而這個方法能回撥多次;
好,對它有個初步瞭解之後,來看看它是怎麼檢測這些事件的。
1,onDown
這個類的程式碼很少,不到800行(SDK28)。
首先來看onDown方法是在什麼時候回撥的 (可在剛剛的介面方法中
CTRL + Click
對應的方法名來定位到具體呼叫的位置) :
超級簡單,監聽到
ACTION_DOWN
事件就立即回撥了。
2,onShowPress
接著來看看
onShowPress
方法(用剛剛說的方法來定位):
它會在
GestureHandler
收到
what
為
SHOW_PRESS
的訊息後回撥,看看在哪裡發的這個訊息:
emmm,同樣在收到
ACTION_DOWN
後,會向
mHandler
(也就是
GestureHandler
)傳送一個指定時間的訊息,而這個時間就是事件按下的時間加上
TAP_TIMEOUT
的時長,可以看到
TAP_TIMEOUT
的值是根據
ViewConfiguration
的
getTapTimeout
方法來獲取的,點開一看:
是100(ms),也就是說,當手指按下後,如果這個延時任務100毫秒內沒有被取消,那麼
onShowPress
方法就會回撥。
3,onSingleTapUp & onScroll
好,現在來看看
onSingleTapUp
:
可以看到,它是在
ACTION_UP
的時候回撥的,回撥需滿足三個條件,分別是:
1.mIsDoubleTapping為false(即雙擊事件未觸發);
2.mInLongPress為false(即長按事件未觸發);
3.mAlwaysInTapRegion為true;
mAlwaysInTapRegion
什麼時候為
true
,什麼時候為
false
呢:
一共有三處賦值的地方,分別是:
1.
ACTION_POINTER_DOWN
(另一隻手指按下)時為
false
,也就是說,如果第一隻手指按下後,100毫秒內有新的手指按下,那麼當手指抬起時不會觸發
onSingleTapUp
;
2.
ACTION_DOWN
(第一隻手指按下)時為
true
;
3.
ACTION_MOVE
時,看
else if
裡面的那個
if
, 它是判斷
distance
(手指的移動距離)是否大於
slopSquare
(觸發移動的最小距離),如果是的話,會回撥
onScroll
方法,並把
mAlwaysInTapRegion
設為
false
,這就說明,如果手指按下100秒內開始了拖動的話,那麼
onSingleTapUp
方法也是不會回撥的;
還可以看到當
mAlwaysInTapRegion
被設為
false
之後,下一次的
ACTION_MOVE
到來時,如果沒有觸發雙擊(即上面的
mIsDoubleTapping為false
)並且手指的水平或垂直移動距離不為0的話,就會一直回撥
onScroll
方法。
好,現在
onScroll
也講了,輪到
onLongPress
了。
4,onLongPress
跟
onShowPress
方法一樣也是藉助
Handler
的定時訊息機制來實現的,它在收到
LONG_PRESS
的訊息之後,會呼叫
dispatchLongPress
方法,
dispatchLongPress
方法首先會標記
mInLongPress
為
true
(注意:這將會影響到上面說到的
onSingleTapUp
的回撥,因為
onSingleTapUp
的回撥條件是需要
mInLongPress
為
false
的(即未觸發長按事件))
接著就回撥
onLongPress
方法。
那麼
LONG_PRESS
訊息在什麼時候傳送的呢?
也是在
ACTION_DOWN
的時候 :
在傳送訊息之前,會先檢查是否開啟了監聽長按事件,還有取消上一次發出且未執行的長按回撥任務。
可以看到定的時間為事件按下時間加上
getLongPressTimeout
方法返回的時長,預設是500(ms),也就是當手指按下半秒後,
onLongPress
方法就會被回撥,當然了,前提是這個任務沒有被取消。
有以下幾種情況會導致長按回撥任務被取消:
500ms內有新手指按下;
500ms內觸發了
onScroll
,即手指移動超過指定距離;
500ms內手指抬起;
500ms內收到了
ACTION_CANCEL
事件(該
ACTION
一般源自父容器的私自建立);
來看看
onFling
:
5,onFling
跟我們平時處理慣性滾動沒什麼區別,只是它在回撥之前會先判斷滑動的速度 是否大於 指定的最小速度,否則不進行滾行滾動。
好,最後我們來看一下
onSingleTapConfirmed
、
onDoubleTap
、
onDoubleTapEvent
分別是怎麼處理的:
onSingleTapConfirmed、onDoubleTap、onDoubleTapEvent
private class GestureHandler extends Handler { ...... @Override public void handleMessage(Message msg) { switch (msg.what) { ...... case TAP: if (mDoubleTapListener != null) { if (!mStillDown) { mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent); } else { mDeferConfirmSingleTap = true; } } break; ...... } } } public boolean onTouchEvent(MotionEvent ev) { ...... boolean handled = false; switch (action & MotionEvent.ACTION_MASK) { ...... case MotionEvent.ACTION_DOWN: ...... if (isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) { mIsDoubleTapping = true; handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent); handled |= mDoubleTapListener.onDoubleTapEvent(ev); } else { mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT); } mStillDown = true; ...... break; case MotionEvent.ACTION_MOVE: if (mIsDoubleTapping) { handled |= mDoubleTapListener.onDoubleTapEvent(ev); } ...... break; case MotionEvent.ACTION_UP: mStillDown = false; if (mIsDoubleTapping) { handled |= mDoubleTapListener.onDoubleTapEvent(ev); } ...... if (mIsDoubleTapping) { ...... } else if (mInLongPress) { ...... } else if (mAlwaysInTapRegion) { handled = mListener.onSingleTapUp(ev); if (mDeferConfirmSingleTap && mDoubleTapListener != null) { mDoubleTapListener.onSingleTapConfirmed(ev); } } ...... break; ...... } ...... return handled; }
首先看
onSingleTapConfirmed
方法,它在兩個地方有呼叫,分別是:
GestureHandler
收到
TAP
訊息時
處理
ACTION_UP
事件時
當
GestureHandler
收到
TAP
訊息時它會先檢查手指是否已經抬起(
!mStillDown
),如果已經抬起了的話,就會立即呼叫,否則把
mDeferConfirmSingleTap
標記為
true
,表示
onSingleTapConfirmed
方法應在
ACTION_UP
時回撥,可以看到在處理
ACTION_UP
時,如果手指沒有移動過並且沒觸發長按的話,就會判斷
mDeferConfirmSingleTap
是否為
true
,是的話,就會回撥
onSingleTapConfirmed
方法。
接著看
ACTION_DOWN
,它會呼叫
isConsideredDoubleTap
方法來判斷此次事件是否被認定是雙擊,如果不是,就會向
GestureHandler
發一條延時訊息(延時回撥
onSingleTapConfirmed
方法)
如果是的話,就會把
mIsDoubleTapping
標記為
true
,然後依次回撥
onDoubleTap
和
onDoubleTapEvent
。可以看到在
ACTION_MOVE
和
ACTION_UP
中也會根據
mIsDoubleTapping
來判斷是否繼續回撥
onDoubleTapEvent
方法。
好,那現在來看一下,它究竟是怎麼認定為雙擊的,看看
isConsideredDoubleTap
方法:
先是判斷了
mAlwaysInBiggerTapRegion
,如果它為
false
的話,則代表被其他動作(
ACTION_MOVE
、
ACTION_CANCEL
)中斷了雙擊事件的檢測,所以直接返回
false
(即不認定是雙擊)了。
接著會判斷第二次按下與第一次按下的時間間隔,如果大於300毫秒則認定是超時,如果小於40毫秒也會忽略(太快了)。
最後,可能很多同學咋一看,看不出來是什麼邏輯,仔細看幾次,就會知道這其實是在計算第一次按下和第二次按下的座標間隔距離,用的就是計算兩點間距離的公式(√(x1 - x2)² + (y1 - y2)²)。
這時有同學可能會問:
不是還要開平方嗎?怎麼它程式碼裡沒有呢?
其實,那個
slopSquare
(也就是能夠被認定為雙擊的最大間隔)在初始化時,就已經作了平方運算了,所以這裡就不需要開平方了。
emmm,那麼
isConsideredDoubleTap
方法最後一句的意思就是,判斷兩次觸控事件的座標間隔是否在指定的最大間隔範圍內,如果是的話,則認定是雙擊。
最後附上我的Android核心技術學習大綱,獲取相關內容來我的GitHub一起玩耍:
你把你的時間投資在學習上,就意味著你可以收穫技能,更有機會增加收入。
在這裡分享我的 來學習,這份Android學習PDF大全真的包含了方方面面了,內含Java基礎知識點、Android基礎、Android進階延伸、演算法合集等等
Android相關學習內容:
關注我看個人介紹,或者直接
私信我
我的這份學習合集,可以有效的幫助大家掌握知識點。
總之也是在這裡幫助大家學習提升進階,也節省大家在網上搜尋資料的時間來學習,也可以分享給身邊好友一起學習
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952849/viewspace-2683388/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 史上最簡單的推薦系統設計
- 為什麼說程式碼註釋是程式設計師必備的技能?程式設計師
- 號稱史上最牛X的程式設計師簡歷,萬千辛酸匯聚於此程式設計師
- @程式設計師,請掌握這些核心生存技能程式設計師
- 成為全棧程式設計師的技能表 - dev全棧程式設計師dev
- 程式設計師不會SQL?骨灰級工程師:全等著被淘汰吧!這是必會技能!程式設計師SQL工程師
- 反轉!以視訊搜視訊,這批 AI 程式設計師要逆天了!AI程式設計師
- 一個成熟的程式設計師必會技能:賺錢程式設計師
- 為什麼程式設計師會有最喜歡與最討厭的程式語言?(earthly)程式設計師
- 作為程式設計師為什麼要閱讀原始碼程式設計師原始碼
- 程式設計師的事,怎麼能叫抄呢?程式設計師
- 程式設計師如何獲取新程式設計技能程式設計師
- 程式設計師的一天,最離不開的竟是“TA”?程式設計師
- 一個合格的web前端程式設計師要學會哪些技能?Web前端程式設計師
- [趣圖]程式設計社群調查顯示,Java程式設計師最苦逼,C++程式設計師最年老,是這樣的麼?Java程式設計師C++
- Java程式設計師必備技能Java程式設計師
- “不會SQL,幹啥都不行!”資深研發:這是程式設計師的必備技能!SQL程式設計師
- 程式設計師面試所需的軟技能程式設計師面試
- .Net WEB 程式設計師需要掌握的技能Web程式設計師
- Java程式設計師需要學習的技能Java程式設計師
- 為什麼程式設計師應該儘早投資於決策技能 - Reforge程式設計師
- 「程式設計羽錄」上線,程式設計師必備的這些技能你能get到嘛?程式設計師
- 如何成為合格的web前端程式設計師?只要學會這幾點!Web前端程式設計師
- 程式設計師必備Python技能!不會?究竟有多可怕....程式設計師Python
- 我的天!這是史上最爛的專案:苦撐12年,600多萬行程式碼...行程
- 一個專為程式設計師設計的精緻 Java 部落格系統程式設計師Java
- 關於程式設計師這個職業程式設計師
- 他可能是全球最會說唱的程式設計師程式設計師
- 好程式設計師Java培訓分享Java程式設計師技能提升指南程式設計師Java
- 遊戲中的技能系統設計遊戲
- 史上最幸運程式設計師:上個月被裁拿賠償,這個月找到漲薪50%的工作程式設計師
- Git算不算程式設計師的必備技能?Git程式設計師
- 程式設計師何苦為難程式設計師?程式設計師
- 程式設計師修神之路--分散式系統設計理念這麼難學?程式設計師分散式
- 老生常談!程式設計師為什麼要閱讀原始碼?程式設計師原始碼
- 歷經 20 天,我終於完成了這份專為程式設計師編寫的英語學習指南程式設計師
- VsCode成為Python程式設計師最喜歡使用的IDEVSCodePython程式設計師IDE
- 屬於每個程式設計師的節日,1024程式設計師節程式碼敲響世界程式設計師