Android 手勢檢測,主要是 GestureDetector相關內容的用法和注意事項,本文依舊屬於事件處理這一體系,部份內容會涉及到以前文章說起過的知識點ide
在開發 Android 手機應用過程當中,可能須要對一些手勢做出響應,如:單擊、雙擊、長按、滑動、縮放等。這些都是很經常使用的手勢。就拿最簡單的雙擊來講吧,假如咱們須要判斷一個控件是否被雙擊(即在較短的時間內快速的點擊兩次),彷佛是一個很容易的任務,但仔細考慮起來,要處理的細節問題也有很多,例如:函數
記錄點擊次數,爲了判斷是否被點擊超過 1 次,因此必須記錄點擊次數。oop
記錄點擊時間,因爲雙擊事件是較快速的點擊兩次,像點擊一次後,過來幾分鐘再點擊一次確定不能算是雙擊事件,因此在記錄點擊次數的同時也要記錄上一次的點擊時間,咱們能夠設置本次點擊距離上一次時間超過必定時間(例如:超過100ms)就不識別爲雙擊事件。測試
點擊狀態重置,在響應雙擊事件,或者判斷不是雙擊事件的時候要重置計數器和上一次點擊時間。重置既能夠在點擊的時候判斷並進行從新設置,也可使用定時器等超過必定時間後重置狀態。this
這樣看起來,判斷一個雙擊事件就有這麼多麻煩事情,更別其餘的手勢了,雖然這些看起來都很簡單,但設計起來須要考慮的細節狀況實在是太多了。spa
那麼有沒有一種更好的方法來方便的檢測手勢呢?固然有啦,由於這些手勢很經常使用,系統早就封裝了一些方法給咱們用,接下來咱們就看看它們是如何使用的。線程
GestureDetector翻譯
GestureDetector 可使用 MotionEvents 檢測各類手勢和事件。設計
GestureDetector.OnGestureListener 是一個回調方法,在發生特定的事件時會調用 Listener 中對應的方法回調。這個類只能用於檢測觸摸事件的 MotionEvent,不能用於軌跡球事件。 (話說軌跡球已經消失多長時間了,估計不少人都沒見過軌跡球這種東西)。 如何使用:日誌
建立一個 GestureDetector 實例。
在onTouchEvent(MotionEvent)方法中,確保調用 GestureDetector 實例的 onTouchEvent(MotionEvent)。回調中定義的方法將在事件發生時執行。
若是偵聽 onContextClick(MotionEvent),則必須在 View 的 onGenericMotionEvent(MotionEvent)中調用 GestureDetector OnGenericMotionEvent(MotionEvent)。
GestureDetector 自己的方法比較少,使用起來也很是簡單,下面讓咱們先看一下它的簡單使用示例,分解開來大概須要三個步驟。
// 1.建立一個監聽回調
SimpleOnGestureListener listener = new SimpleOnGestureListener() {
@Override public boolean onDoubleTap(MotionEvent e) {
Toast.makeText(MainActivity.this, "雙擊666", Toast.LENGTH_SHORT).show();
return super.onDoubleTap(e);
}
};
// 2.建立一個檢測器
final GestureDetector detector = new GestureDetector(this, listener);
// 3.給監聽器設置數據源
view.setOnTouchListener(new View.OnTouchListener() {
@Override public boolean onTouch(View v, MotionEvent event) {
return detector.onTouchEvent(event);
}
});
複製代碼
接下來咱們先了解一下 GestureDetector 裏面都有哪些內容。
GestureDetector 一共有 5 種構造函數,但有 2 種被廢棄了,1 種是重複的,因此咱們只須要關注其中的 2 種構造函數便可,以下:
構造函數GestureDetector(Context context, GestureDetector.OnGestureListener listener)GestureDetector(Context context, GestureDetector.OnGestureListener listener, Handler handler)
第 1 種構造函數裏面須要傳遞兩個參數,上下文(Context) 和 手勢監聽器(OnGestureListener),這個很容易理解,就再也不過多敘述,上面的例子中使用的就是這一種。
第 2 種構造函數則須要多傳遞一個 Handler 做爲參數,這個有什麼做用呢?其實做用也很是簡單,這個 Handler 主要是爲了給 GestureDetector 提供一個 Looper。
在一般狀況下是不需這個 Handler 的,由於它會在內部自動建立一個 Handler 用於處理數據,若是你在主線程中建立 GestureDetector,那麼它內部建立的 Handler 會自動得到主線程的 Looper,然而若是你在一個沒有建立 Looper 的子線程中建立 GestureDetector 則須要傳遞一個帶有 Looper 的 Handler 給它,不然就會由於沒法獲取到 Looper 致使建立失敗。
第 2 種構造函數使用方式以下(下面是兩種在子線程中建立 GestureDetector 的方法):
// 方式1、在主線程建立 Handler
final Handler handler = new Handler();
new Thread(new Runnable() {
@Override public void run() {
final GestureDetector detector = new GestureDetector(MainActivity.this, new
GestureDetector.SimpleOnGestureListener() , handler);
// ... 省略其它代碼 ...
}
}).start();
// 方式2、在子線程建立 Handler,而且指定 Looper
new Thread(new Runnable() {
@Override public void run() {
final Handler handler = new Handler(Looper.getMainLooper());
final GestureDetector detector = new GestureDetector(MainActivity.this, new
GestureDetector.SimpleOnGestureListener() , handler);
// ... 省略其它代碼 ...
}
}).start();
複製代碼
固然了,使用其它建立 Handler 的方式也是能夠的,重點傳遞的 Handler 必定要有 Looper,敲黑板,重點是 Handler 中的 Looper。假如子線程準備了 Looper 那麼能夠直接使用第 1 種構造函數進行建立,以下:
new Thread(new Runnable() {
@Override public void run() {
Looper.prepare(); // <- 重點在這裏
final GestureDetector detector = new GestureDetector(MainActivity.this, new
GestureDetector.SimpleOnGestureListener());
// ... 省略其它代碼 ...
}
}).start();
複製代碼
2.手勢監聽器
既然是手勢檢測,天然要在對應的手勢出現的時候通知調用者,最合適的天然是事件監聽器模式。目前 GestureDetecotr 有四種監聽器。
監聽器簡介OnContextClickListener這個很容易讓人聯想到ContextMenu,然而它和ContextMenu並無什麼關係,它是在Android6.0(API 23)才添加的一個選項,是用於檢測外部設備上的按鈕是否按下的,例如藍牙觸控筆上的按鈕,通常狀況下,忽略便可。OnDoubleTapListener雙擊事件,有三個回調類型:雙擊(DoubleTap)、單擊確認(SingleTapConfirmed) 和 雙擊事件回調(DoubleTapEvent)OnGestureListener手勢檢測,主要有如下類型事件:按下(Down)、 一扔(Fling)、長按(LongPress)、滾動(Scroll)、觸摸反饋(ShowPress) 和 單擊擡起(SingleTapUp)SimpleOnGestureListener這個是上述三個接口的空實現,通常狀況下使用這個比較多,也比較方便。
2.1 OnContextClickListener
因爲 OnContextClickListener 主要是用於檢測外部設備按鈕的,關於它須要注意一點,若是偵聽 onContextClick(MotionEvent),則必須在 View 的 onGenericMotionEvent(MotionEvent)中調用 GestureDetector 的 OnGenericMotionEvent(MotionEvent)。 因爲目前咱們用到這個監聽器的場景並很少,因此也就不展開介紹了,重點關注後面幾個監聽器。
2.2 OnDoubleTapListener
這個很明顯就是用於檢測雙擊事件的,它有三個回調接口,分別是 onDoubleTap、onDoubleTapEvent 和 onSingleTapConfirmed。
2.2.1 onDoubleTap 與 onSingleTapConfirmed
若是你只想監聽雙擊事件,那麼只用關注 onDoubleTap 就好了,若是你同時要監聽單擊事件則須要關注 onSingleTapConfirmed 這個回調函數。
有人可能會有疑問,監聽單擊事件爲何要使用 onSingleTapConfirmed,使用 OnClickListener 不行嗎?從理論上是可行的,可是我並不推薦這樣使用,主要有兩個緣由:
1.它們兩個是存在必定衝突的,若是你看過 事件分發機制詳解 就會知道,若是想要二者同時被觸發,則 setOnTouchListener 不能消費事件,若是 onTouchListener 消費了事件,就可能致使 OnClick 沒法正常觸發。
2.須要同時監聽單擊和雙擊,則說明單擊和雙擊後響應邏輯不一樣,然而使用 OnClickListener 會在雙擊事件發生時觸發兩次,這顯然不是咱們想要的結果。而使用 onSingleTapConfirmed 就不用考慮那麼多了,你徹底能夠把它當成單擊事件來看待,並且在雙擊事件發生時,onSingleTapConfirmed 不會被調用,這樣就不會引起衝突。
若是你須要同時監聽兩種點擊事件能夠這樣寫:
GestureDetector detector = new GestureDetector(this, new GestureDetector
.SimpleOnGestureListener() {
@Override public boolean onSingleTapConfirmed(MotionEvent e) {
Toast.makeText(MainActivity.this, "單擊", Toast.LENGTH_SHORT).show();
return false;
}
@Override public boolean onDoubleTap(MotionEvent e) {
Toast.makeText(MainActivity.this, "雙擊", Toast.LENGTH_SHORT).show();
return false;
}
});
複製代碼
關於 onSingleTapConfirmed 原理也很是簡單,這一個回調函數在單擊事件發生後300ms後觸發(注意,不是當即觸發的),只有在肯定不會有後續的事件後,既當前事件確定是單擊事件才觸發 onSingleTapConfirmed,因此在進行點擊操做時,onDoubleTap 和 onSingleTapConfirmed 只會有一個被觸發,也就不存在衝突了。
固然,若是你對事件分發機制很是瞭解的話,隨便怎麼用都行,條條大路通羅馬,我這裏只是推薦一種最簡單並且不容易出錯的實現方案。
2.2.2 onDoubleTapEvent
有些細心的小夥伴可能注意到還有一個 onDoubleTapEvent 回調函數,它是幹什麼的呢?它在雙擊事件肯定發生時會對第二次按下產生的 MotionEvent 信息進行回調。
至於爲何要存在這樣的回調,就要涉及到另外一個比較細緻的問題了,那就是 onDoubleTap 的觸發時間,若是你在這些函數被調用時打印一條日誌,那麼你會看到這樣的信息:
GCS-LOG: onDoubleTap
GCS-LOG: onDoubleTapEvent - down
GCS-LOG: onDoubleTapEvent - move
GCS-LOG: onDoubleTapEvent - move
GCS-LOG: onDoubleTapEvent - up
複製代碼
經過觀察這些信息你會發現它們的調用順序很是有趣,首先是 onDoubleTap 被觸發,以後依次觸發 onDoubleTapEvent 的 down、move、up 等信息,爲何說它們有趣呢?是由於這樣的調用順序會引起兩種猜測,第一種猜測是 onDoubleTap 是在第二次手指擡起(up)後觸發的,而 onDoubleTapEvent 是一種延時回調。第二種猜測則是 onDoubleTap 在第二次手指按下(dowm)時觸發,onDoubleTapEvent 是一種實時回調。
經過測試和觀察源碼發現第二種猜測是正確的,由於第二次按下手指時,即使不擡起也會觸發 onDoubleTap 和 onDoubleTapEvent 的 down,並且源碼中邏輯也代表 onDoubleTapEvent 是一種實時回調。
這就引起了另外一個問題,雙擊的觸發時間,雖然這是一個細微到很難讓人注意到的問題,假如說咱們想要在第二次按下擡起後才斷定這是一個雙擊操做,觸發後續的內容,則不能使用 onDoubleTap 了,須要使用 onDoubleTapEvent 來進行更細微的控制,以下:
final GestureDetector detector = new GestureDetector(MainActivity.this, new GestureDetector.SimpleOnGestureListener() {
@Override public boolean onDoubleTap(MotionEvent e) {
Logger.e("第二次按下時觸發");
return super.onDoubleTap(e);
}
@Override public boolean onDoubleTapEvent(MotionEvent e) {
switch (e.getActionMasked()) {
case MotionEvent.ACTION_UP:
Logger.e("第二次擡起時觸發");
break;
}
return super.onDoubleTapEvent(e);
}
});
複製代碼
若是你不須要控制這麼細微的話,忽略便可(Logger 是我本身封裝的日誌庫,忽略便可)。
2.3 OnGestureListener
這個是手勢檢測中較爲核心的一個部分了,主要檢測如下類型事件:按下(Down)、 一扔(Fling)、長按(LongPress)、滾動(Scroll)、觸摸反饋(ShowPress) 和 單擊擡起(SingleTapUp)。
2.3.1 onDown
@Override public boolean onDown(MotionEvent e) {
return true;
}
複製代碼
看過前面的文章應該知道,down 在事件分發體系中是一個較爲特殊的事件,爲了保證事件被惟一的 View 消費,哪一個 View 消費了 down 事件,後續的內容就會傳遞給該 View。若是咱們想讓一個 View 可以接收到事件,有兩種作法:
一、讓該 View 能夠點擊,由於可點擊狀態會默認消費 down 事件。
二、手動消費掉 down 事件。
因爲圖片、文本等一些控件默認是不可點擊的,因此咱們要麼聲明它們的 clickable 爲 true,要麼在發生 down 事件是返回 true。因此 onDown 在這裏的做用就很明顯了,就是爲了保證讓該控件能擁有消費事件的能力,以接受後續的事件。
2.3.2 onFling
Failing 中文直接翻譯過來就是一扔、拋、甩,最多見的場景就是在 ListView 或者 RecyclerView 上快速滑動時手指擡起後它還會滾動一段時間纔會中止。onFling 就是檢測這種手勢的。
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float
velocityY) {
return super.onFling(e1, e2, velocityX, velocityY);
}
複製代碼
在 onFling 的回調中共有四個參數,分別是:
參數簡介e1手指按下時的 Event。e2手指擡起時的 Event。velocityX在 X 軸上的運動速度(像素/秒)。velocityY在 Y 軸上的運動速度(像素/秒)。
咱們能夠經過 e1 和 e2 獲取到手指按下和擡起時的座標、時間等相關信息,經過 velocityX 和 velocityY 獲取到在這段時間內的運動速度,單位是像素/秒(即 1 秒內滑動的像素距離)。
這個咱們本身用到的地方比較少,可是也能夠幫助咱們簡單的作出一些有趣的效果,例以下面的這種彈球效果,會根據滑動的力度和方向產生不一樣的彈跳效果。
其實這種原理很是簡單,簡化以後以下:
記錄 velocityX 和 velocityY 做爲初始速度,以後不斷讓速度衰減,直至爲零。
根據速度和當前小球的位置計算一段時間後的位置,並在該位置從新繪製小球。
判斷小球邊緣是否碰觸控件邊界,若是碰觸了邊界則讓速度反向。
根據這三條基本的邏輯就能夠作出比較像的彈球效果
2.3.3 onLongPress
這個是檢測長按事件的,即手指按下後不擡起,在一段時間後會觸發該事件。
@Override
public void onLongPress(MotionEvent e) {
}
複製代碼
2.3.4 onScroll
onScroll 就是監聽滾動事件的,它看起來和 onFaling 比較像,不一樣的是,onSrcoll 後兩個參數不是速度,而是滾動的距離。
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float
distanceY) {
return super.onScroll(e1, e2, distanceX, distanceY);
}
複製代碼
參數e1手指按下時的Evente2手指擡起時的EventdistanceX在 X 軸上劃過的距離distanceY在 Y 軸上劃過的距離
2.3.5 onShowPress
它是用戶按下時的一種回調,主要做用是給用戶提供一種視覺反饋,能夠在監聽到這種事件時可讓控件換一種顏色,或者產生一些變化,告訴用戶他的動做已經被識別。
不過這個消息和 onSingleTapConfirmed 相似,也是一種延時回調,延遲時間是 180 ms,假如用戶手指按下後當即擡起或者事件當即被攔截,時間沒有超過 180 ms的話,這條消息會被 remove 掉,也就不會觸發這個回調。
@Override
public void onShowPress(MotionEvent e) {
}
複製代碼
2.3.6 onSingleTapUp
@Override
public boolean onSingleTapUp(MotionEvent e) {
return super.onSingleTapUp(e);
複製代碼
} 這個也很容易理解,就是用戶單擊擡起時的回調,可是它和上面的 onSingleTapConfirmed 之間有何不一樣呢?和 onClick又有何不一樣呢?
單擊事件觸發:
GCS: onSingleTapUp
GCS: onClick
GCS: onSingleTapConfirmed
複製代碼
類型觸發次數摘要onSingleTapUp1單擊擡起onSingleTapConfirmed1單擊確認onClick1單擊事件
雙擊事件觸發:
GCS: onSingleTapUp
GCS: onClick
GCS: onDoubleTap // <- 雙擊
GCS: onClick
複製代碼
類型觸發次數摘要onSingleTapUp1在雙擊的第一次擡起時觸發onSingleTapConfirmed0雙擊發生時不會觸發。onClick2在雙擊事件時觸發兩次。
能夠看出來這三個事件仍是有所不一樣的,根據本身實際須要進行使用便可
2.4 SimpleOnGestureListener
這個裏面並無什麼內容,只是對上面三種 Listener 的空實現,在上面的例子中使用的基本都是這監聽器。由於它用起來更方便一點。 這主要是 GestureDetector 構造函數的設計問題,以只監聽 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, "單擊確認", Toast.LENGTH_SHORT).show();
return false;
}
@Override public boolean onDoubleTap(MotionEvent e) {
Toast.makeText(MainActivity.this, "雙擊", Toast.LENGTH_SHORT).show();
return false;
}
@Override public boolean onDoubleTapEvent(MotionEvent e) {
// Toast.makeText(MainActivity.this,"",Toast.LENGTH_SHORT).show();
return false;
}
});
複製代碼
既然都已經建立 SimpleOnGestureListener 了,再建立一個 OnDoubleTapListener 顯然十分浪費,若是構造函數不使用 SimpleOnGestureListener,而是使用 OnGestureListener 的話,會多出幾個無用的空實現,顯然很浪費,因此在通常狀況下,老老實實的使用 SimpleOnGestureListener 就行了。
除了各種監聽器以外,與 GestureDetector 相關的方法其實並很少,只有幾個,下面來簡單介紹一下。
方法摘要setIsLongpressEnabled經過布爾值設置是否容許觸發長按事件,true 表示容許,false 表示不容許。isLongpressEnabled判斷當前是否容許觸發長按事件,true 表示容許,false 表示不容許。onTouchEvent這個是其中一個重要的方法,在最開始已經演示過使用方式了。onGenericMotionEvent這個是在 API 23 以後才添加的內容,主要是爲 OnContextClickListener 服務的,暫時不用關注。setContextClickListener設置 ContextClickListener 。setOnDoubleTapListener設置 OnDoubleTapListener 。
結語
關於手勢檢測部分的 GestureDetector 相關內容基本就這麼多了,其實手勢檢測還有一個 ScaleGestureDetector 也是爲手勢檢測服務的。