在 android 開發過程當中,咱們常常須要對一些手勢,如:單擊、雙擊、長按、滑動、縮放等,進行監測。這時也就引出了手勢監測的概念,所謂的手勢監測,說白了就是對於 GestureDetector 的用法的使用和注意要點的學習。注:因爲縮放手勢獨有的複雜性,我打算後期將其單獨拿出來概括總結。java
像網上其餘將手勢監聽的博客同樣,本文將以雙擊事件爲引子,逐步展開探討 Android 手勢監聽,你須要知道的點點滴滴,仍是那句話:看完這篇還不會 GestureDetector 手勢檢測,我跪搓衣板!android
對於一個 Android 新手而言,若是須要你實現一個雙擊功能,咱們通常會怎麼想呢?ide
May Beoop
But
這實在是太複雜了,你又要控制時間,又要判斷控件等等等等。因此,咱們因該如何解決呢?手勢監聽的使用post
個人理解是 GestureDetector 是 Android 中,專門用來進行手勢監聽的一個對象,在他的監聽器中,咱們經過傳入 MotionEvents 對象,就能夠在各類事件的回調方法中各類手勢進行監測。舉個例子: GestureDetector 的 OnGestureListener 就是一種回調方法,就是說在得到了傳入的這個 MotionEvents 對象以後,進行了處理,咱們經過重寫了其中的各類方法(單擊事件、雙擊事件等等),就能夠監聽到單擊,雙擊,滑動等事件,而後直接在這些方法內部進行處理。學習
簡單易懂,一分鐘搞定this
@Override protected void onResume() { button.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return detector.onTouchEvent(event); } }); super.onResume(); } private void iniGestureListener(){ GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){ @Override public boolean onDoubleTap(MotionEvent e) { MyToast.makeToast(GestureDetectorActivity.this, "double click up!"); return super.onDoubleTap(e); } detector = new GestureDetector(GestureDetectorActivity.this, listener); }
運行結果線程
若是哪位好奇的老鐵,嘗試着在線程中建立這個 detector 對象(好比下面這種)。那麼運行時就可能出現程序崩潰的狀況,這是爲何呢?翻譯
new Thread(){ @Override public void run() { super.run(); detector = new GestureDetector(GestureDetectorActivity.this, listener); } }.start();
其實在 GestureDetector 被實例化時,內部會自動建立一個 Handler 用於處理數據,因此若是你在主線程中建立 GestureDetector,那麼這個 GestureDetector 內部建立的 Handler 會自動得到主線程的 Looper。也是所以:若是你在一個沒有建立 Looper 的子線程中建立 GestureDetector 則須要傳遞一個帶有 Looper 的 Handler 給它,不然就會由於沒法獲取到 ==Looper== 致使建立失敗。code
既然問題出現了,那要怎麼解決呢。既然是缺乏活動中的 Looper ,那麼將活動中的 ==Looper== 傳入就是。觀察 ==detector== 的構造方法,發現其一共有種方法,其中咱們經常使用的方法有兩種,首先是咱們在主線程中用的那種,另一種就是咱們如今要用的,在子線程中,能傳入 Looper 的 構造方法:
| public GestureDetector(Context context, OnGestureListener listener) |
|--|
| GestureDetector(Context context, GestureDetector.OnGestureListener listener, Handler handler) |
方案一
傳入一個保有 Looper 對象的 Hander
new Thread(){ @Override public void run() { super.run(); detector = new GestureDetector(GestureDetectorActivity.this, listener, new Handler(Looper.getMainLooper())); } }.start();
方案二
跟方法已同樣,只是把 Hander 拿出來單首創建罷了
new Thread(){ @Override public void run() { super.run(); Handler handler = new Handler(Looper.getMainLooper()); detector = new GestureDetector(GestureDetectorActivity.this, listener, handler); } }.start();
方案三
在主線程中建立 Hander ,這樣就不用在建立 Hander 時,傳入主線程的 Looper
final Handler handler = new Handler(); new Thread(){ @Override public void run() { super.run(); detector = new GestureDetector(GestureDetectorActivity.this, listener, handler); } }.start();
方案四
和上面幾個方法同樣,只不過在子線稱裏提早準備好 Lopper ,這樣子線稱就和主線程同樣了
new Thread(){ @Override public void run() { super.run(); Looper.prepare(); detector = new GestureDetector(GestureDetectorActivity.this, listener); } }.start();
剛剛咱們已經經過雙擊效果,講過 onDoubleTapEvent 了,那麼 GestureDetecotr 還有哪些厲害的回調方法呢?
咱們先來說講 OnDoubleTapListener,你們可能要問:剛剛不是已經講過雙擊事件監聽了嗎,這裏又來不是浪費時間?廢話不說,讓我詳細介紹下這類的方法:
有人就會很好奇,對於單擊事件的回調,直接去用 onClickListener 不就行了麼,幹嗎要用 SingleTapConfirmed 呢?
首先,這兩個方法是衝突的,這裏就涉及到了事件分發機制,這點我後期會專門給你們總結下,這裏就不詳解了。
其二,更具 onClickListener 的機制,咱們不難發現,若是是用 onClickListener 的話,當咱們雙擊時,咱們也會調用單擊事件,也就是單擊了兩次,這明顯是不符合咱們意圖的。那麼該如何調用呢?very easy !
final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){ @Override public boolean onSingleTapConfirmed(MotionEvent e) { MyToast.makeToast(GestureDetectorActivity.this, "single click!"); return super.onSingleTapConfirmed(e); } ... };
我打算把這兩個方法放在一塊兒將,一則他兩都屬於雙擊的範疇,二則他兩有着極高類似和細微卻重要的區別。
你們能夠嘗試着在 onTouchEvent 和 DoubleTap 中,對點擊的 Down move 和 up 進行打印,你就會發現,對於 DoubleTap 而言,它是在第二次點擊按下是,發生的回調,而對於 onDoubleTapEvent 而言,則是在第二次點擊後,手指擡起離開了屏幕時,發生的回調。這就是他兩最重要的區別。
final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){ @Override public boolean onDoubleTap(MotionEvent e) { MyToast.makeToast(GestureDetectorActivity.this, "double click down!"); return super.onDoubleTap(e); } @Override public boolean onDoubleTapEvent(MotionEvent e) { switch (e.getActionMasked()){ case MotionEvent.ACTION_UP: MyToast.makeToast(GestureDetectorActivity.this, "double click up!"); break; } return super.onDoubleTapEvent(e); } };
因此,有了這兩個方法,咱們就能夠更具目的性的知足兩種需求。
講到這裏,單擊雙擊事件就告一段落了,下面咱們進入 OnGestureListener 的學習
這能夠說是整個手勢監測中,最核心的部分了,前面都是引入,如今纔是正題,這裏我主要向你們介紹一下手勢:
onDown 事件很好理解,他在一個 View 被按下時執行。也正是如此,要想能執行 onDown ,首先要保證這個 View 是能夠點擊的,也就是 onClickable 的值爲 true 。
private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){ @Override public boolean onDown(MotionEvent e) { MyToast.makeToast(GestureDetectorActivity.this, "onDown"); // 後續事件 return super.onDown(e); } };
對於 onFling 我我的感受這是個最經常使用的方法,就像它的名字,翻譯過來是拖、拽、扔的意思。舉個例子 RecyclerView 或者 ListView 咱們都有用過,當咱們快速上拉後會滾動必定距離中止,咱們可愛的 onFling 就是用於檢測這種手勢的。
private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){ @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { mSpeedX = velocityX; mSpeedY = velocityY; handler.postDelayed(runnable, 30); return super.onFling(e1, e2, velocityX, velocityY); } };
從代碼中,咱們不難發現:該方法有四個參數
參數 | 意義 |
---|---|
e1 | 手指按下時的 Event。 |
e2 | 手指擡起時的 Event。 |
velocityX | 在 X 軸上的運動速度(像素/秒)。 |
velocityY | 在 Y 軸上的運動速度(像素/秒)。 |
經過前兩個 MotionEvent 參數,咱們能夠得到點擊發生的位置等,經過後兩個 float 參數,咱們能夠得到手指滑動的速度。
具體使用其實仍是蠻多的,好比咱們能夠想象下臺球遊戲,球杆擊球后,就有這樣一個初速度遞減的效果。
onLongPress 很簡單,就是長按事件的回調,好比說長按複製,長按彈窗等等,它不但應用普遍,同時使用也很是簡單,這裏就不嘮叨了
private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){ @Override public void onLongPress(MotionEvent e) { MyToast.makeToast(GestureDetectorActivity.this, "onLongPress"); // 後續工做 super.onLongPress(e); } };
onScroll 方法和 onFling 很像,惟一的區別在於,onFling 的參數是滑動的速度,而 onScroll 的後兩個參數則是滑動的距離:
參數 | 意義 |
---|---|
e1 | 手指按下時的 MotionEvent |
e2 | 手指擡起時的 MotionEvent |
distanceX | 在 X 軸上劃過的距離 |
distanceY | 在 Y 軸上劃過的距離 |
private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){ @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { MyToast.makeToast(GestureDetectorActivity.this, "onScroll X = " + distanceX + " Y = " + distanceY); return super.onScroll(e1, e2, distanceX, distanceY); } };
這個方法我其實以爲做用不是很大,由於它是在 View 被點擊(按下)是調用,其做用是給用戶一個視覺反饋,讓用戶知道我這個控件被點擊了,這樣的效果咱們徹底能夠用 Material design 的 ripple 實現,或者直接 drawable 寫個背景也行。
若是說它有什麼特別指出的話,它是一種延時回調,延遲時間是 180 ms。也就是說用戶手指按下後,若是當即擡起或者事件當即被攔截,時間沒有超過 180 ms的話,這條消息會被 remove 掉,也就不會觸發這個回調。
private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){ @Override public void onShowPress(MotionEvent e) { MyToast.makeToast(GestureDetectorActivity.this, "onShowPress");// >150ms 時調用 super.onShowPress(e); } };
對於 onSingleTapUp 網上有不少分析,但我以爲過於複雜了,其實這東西很簡單。舉個例子你就懂了:
以前咱們講過雙擊事件,那好 onSingleTapUp 就是在 雙擊事件的第一次點擊時回調。也就是說但你點擊了一個控件時(雙擊第一下),這個回調立刻會被調用,而後迅速點第二下(雙擊事件的第二下),則其不會被調用。
類型 | 觸發次數 | 摘要 |
---|---|---|
onSingleTapUp | 1 | 在雙擊的第一次擡起時觸發 |
onSingleTapConfirmed | 0 | 雙擊發生時不會觸發。 |
onClick | 2 | 在雙擊事件時觸發兩次。 |
它和 onSingleTapConfirmed 的區別也就很明顯了,onSingleTapConfirmed 在發生雙擊時,會回調兩次,而 onSingleTapUp 只會在雙擊的的第一次回調。
private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){ @Override public boolean onSingleTapUp(MotionEvent e) {// 雙擊第一次擡起觸發,第二次不觸發 Log.d("onSingleTapUp", "onSingleTapUp");// >150ms 時調用 return super.onSingleTapUp(e); } };
SimpleOnGestureListener 中包含了以上全部方法的空實現,之因此在文末再一次說起他,主要是想講下它的方便之處。
咱們以監聽 OnDoubleTapListener 爲例,若是想要使用 OnDoubleTapListener 接口則須要這樣進行設置:
GestureDetector detector = new GestureDetector(this, new GestureDetector .SimpleOnGestureListener()); detector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() { @Override public boolean onSingleTapConfirmed(MotionEvent e) { Toast.makeText(MainActivity.this, "onSingleTapConfirmed", Toast.LENGTH_SHORT).show(); return false; } @Override public boolean onDoubleTap(MotionEvent e) { Toast.makeText(MainActivity.this, "onDoubleTap", Toast.LENGTH_SHORT).show(); return false; } @Override public boolean onDoubleTapEvent(MotionEvent e) { Toast.makeText(MainActivity.this,"onDoubleTapEvent",Toast.LENGTH_SHORT).show(); return false; } });
咱們不難發現一個問題,既然在 GestureDetector 實例化時,已經實例化了一個 SimpleOnGestureListener 了,那麼在捨近求遠的去使用 OnGestureListener 的話,會多出幾個無用的空實現,顯然很浪費,因此在通常狀況下,乖乖的使用 SimpleOnGestureListener 就行了。
因爲手勢監聽的方法有點多,你們一時難以記住,因此我打算把全部方法,在 SimpleOnGestureListener 中重寫一遍,方便你們進行查閱、記憶:
private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){ @Override public boolean onSingleTapConfirmed(MotionEvent e) { MyToast.makeToast(GestureDetectorActivity.this, "single click!"); return super.onSingleTapConfirmed(e); } @Override public boolean onDoubleTap(MotionEvent e) { MyToast.makeToast(GestureDetectorActivity.this, "double click down!"); return super.onDoubleTap(e); } @Override public boolean onDoubleTapEvent(MotionEvent e) { switch (e.getActionMasked()){ case MotionEvent.ACTION_UP: MyToast.makeToast(GestureDetectorActivity.this, "double click up!"); break; } return super.onDoubleTapEvent(e); } @Override public boolean onDown(MotionEvent e) { MyToast.makeToast(GestureDetectorActivity.this, "onDown"); return super.onDown(e); } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { mSpeedX = velocityX; mSpeedY = velocityY; handler.postDelayed(runnable, 30); return super.onFling(e1, e2, velocityX, velocityY); } @Override public void onShowPress(MotionEvent e) { MyToast.makeToast(GestureDetectorActivity.this, "onShowPress");// >150ms 時調用 super.onShowPress(e); } @Override public boolean onSingleTapUp(MotionEvent e) {// 雙擊第一次擡起觸發,第二次不觸發 Log.d("onSingleTapUp", "onSingleTapUp");// >150ms 時調用 return super.onSingleTapUp(e); } @Override public void onLongPress(MotionEvent e) { MyToast.makeToast(GestureDetectorActivity.this, "onLongPress"); // 後續工做 super.onLongPress(e); } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { MyToast.makeToast(GestureDetectorActivity.this, "onScroll X = " + distanceX + " Y = " + distanceY); return super.onScroll(e1, e2, distanceX, distanceY); } };
本篇博客,是我對我學習過程的總結,因此其中不免有疏漏,但願你們能在評論區中指出,萬分感謝。
同時,若是你們有任何疑問,也能夠在評論區中留言、討論,這個搓衣板跪不跪,大家說了算!🙏