Android點擊事件之多點觸摸與手勢識別的實現(xiàn)
前言
最近遇到想要實現(xiàn)三指滑動監(jiān)聽的需求,實現(xiàn)代碼不方便貼出來,但是思路還是可以記錄一下。
Muilti-touch 雙指縮放探索
首先要實現(xiàn)OnTouchListener接口,然后重寫方法:
public boolean onTouch(View v, MotionEvent event);
從這個方法中我們就可以獲取實現(xiàn)兩指縮放功能的全部信息。
View v是觸發(fā)事件的源,MotionEvent event即一個觸摸事件。對屏幕的幾乎所有操作都會觸發(fā)事件,如點擊、放開、滑動等。
不同的事件在MotionEvent中有不同的id,我們可以根據(jù)event.getAction() & MotionEvent.ACTION_MASK的結(jié)果來判斷是何種事件。
有如下事件使我們要用到的:
- MotionEvent.ACTION_DOWN:在第一個點被按下時觸發(fā)
- MotionEvent.ACTION_UP:當屏幕上唯一的點被放開時觸發(fā)
- MotionEvent.ACTION_POINTER_DOWN:當屏幕上已經(jīng)有一個點被按住,此時再按下其他點時觸發(fā)。
- MotionEvent.ACTION_POINTER_UP:當屏幕上有多個點被按住,松開其中一個點時觸發(fā)(即非最后一個點被放開時)。
- MotionEvent.ACTION_MOVE:當有點在屏幕上移動時觸發(fā)。值得注意的是,由于它的靈敏度很高,而我們的手指又不可能完全靜止(即使我們感覺不到移動,但其實我們的手指也在不停地抖動),所以實際的情況是,基本上只要有點在屏幕上,此事件就會一直不停地被觸發(fā)。
舉例子來說:當我們放一個食指到屏幕上時,觸發(fā)ACTION_DOWN事件;再放一個中指到屏幕上,觸發(fā)ACTION_POINTER_DOWN事件;此時再把食指或中指放開,都會觸發(fā)ACTION_POINTER_UP事件;再放開最后一個手指,觸發(fā)ACTION_UP事件;而同時在整個過程中,ACTION_MOVE事件會一直不停地被觸發(fā)。
event.getX(index)和event.getY(index)可以獲取到指定index點的坐標,所以當屏幕上有兩個點的時候,我們用如下方法來獲取兩點間的距離:
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return FloatMath.sqrt(x * x + y * y);
}
由以上事件觸發(fā)的原理,就可以根據(jù)被觸發(fā)的不同事件來判斷當前屏幕上的點的個數(shù):
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mode = 1;
break;
case MotionEvent.ACTION_UP:
mode = 0;
break;
case MotionEvent.ACTION_POINTER_UP:
mode -= 1;
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode += 1;
break;
}
然后在MotionEvent.ACTION_MOVE事件中,判斷點的個數(shù),如果大于等于2,就計算兩點間的距離,如果距離增大就把圖片放大,距離減少就把圖片縮小。
于是代碼就成了:
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mode = 1;
break;
case MotionEvent.ACTION_UP:
mode = 0;
break;
case MotionEvent.ACTION_POINTER_UP:
mode -= 1;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);//兩點按下時的距離
mode += 1;
break;
case MotionEvent.ACTION_MOVE:
if (mode >= 2) {
float newDist = spacing(event);
if (newDist > oldDist) {
zoomOut();
}
if (newDist < oldDist) {
zoomIn();
}
break;
}
經(jīng)過檢驗,這種方法是能夠?qū)崿F(xiàn)縮放效果的。
但是有了另外一個問題:就是由于ACTION_MOVE會因顫抖一直被觸發(fā),而每次觸發(fā)的時候兩點間的距離也總會有細小的變化,所以運行之后只要有兩點在屏幕上,就總會在放大或縮小字體。
經(jīng)過一番思考,我想出了一個控制其靈敏度的方法,即在case MotionEvent.ACTION_MOVE時判斷只有當距離變化大于一定程度時才會更改字體大小:
if (newDist > oldDist + 1) {//原為:if (newDist > oldDist)
zoomOut();//放大
}
另外縮放的方法也改成了按比例縮放,完整的ZoomListenter代碼:
import android.util.FloatMath;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.TextView;
public class ZoomListenter implements OnTouchListener {
private int mode = 0;
float oldDist;
float textSize = 0;
TextView textView = null;
@Override
public boolean onTouch(View v, MotionEvent event) {
textView = (TextView) v;
if (textSize == 0) {
textSize = textView.getTextSize();
}
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mode = 1;
break;
case MotionEvent.ACTION_UP:
mode = 0;
break;
case MotionEvent.ACTION_POINTER_UP:
mode -= 1;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
mode += 1;
break;
case MotionEvent.ACTION_MOVE:
if (mode >= 2) {
float newDist = spacing(event);
if (newDist > oldDist + 1) {
zoom(newDist / oldDist);
oldDist = newDist;
}
if (newDist < oldDist - 1) {
zoom(newDist / oldDist);
oldDist = newDist;
}
}
break;
}
return true;
}
private void zoom(float f) {
textView.setTextSize(textSize *= f);
}
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return FloatMath.sqrt(x * x + y * y);
}
}
這樣,基本算是能達到預期的效果了。
Android原生帶的手勢監(jiān)聽
GestureDetector 使用
GestureDetector 是 Android 中,專門用來進行手勢監(jiān)聽的一個對象,在他的監(jiān)聽器中,我們通過傳入 MotionEvents 對象,就可以在各種事件的回調(diào)方法中各種手勢進行監(jiān)測。舉個例子: GestureDetector 的 OnGestureListener 就是一種回調(diào)方法,就是說在獲得了傳入的這個 MotionEvents 對象之后,進行了處理,我們通過重寫了其中的各種方法(單擊事件、雙擊事件等等),就可以監(jiān)聽到單擊,雙擊,滑動等事件,然后直接在這些方法內(nèi)部進行處理。

使用方法
首先,創(chuàng)建一個 SimpleOnGestureListener 回調(diào)方法對象,并對其中各個方法進行重寫
根據(jù)這個 listener 對象,實例化出 GestureDetector 對象
對目標控件重寫 setOnTouchListener 方法,并在其中調(diào)用 detector 對象的 onTouchEvent 方法即可
簡單易懂,一分鐘搞定。
@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);
}
GestureDetecotr 還有哪些厲害的回調(diào)方法呢?
- OnDoubleTapListener :也就是雙擊事件,雙擊事件除了 onDoubleTapEvent 這個回調(diào)方法之外,還有 SingleTapConfirmed 和 DoubleTap 這兩個回調(diào)方法
- OnGestureListener :這里集合了眾多手勢的監(jiān)聽器:主要有:按下(Down)、 扔(Fling)、長按(LongPress)、滾動(Scroll)、觸摸反饋(ShowPress) 和 單擊抬起(SingleTapUp)
- SimpleOnGestureListener :上述接口的空實現(xiàn),用的頻率比較多
OnDoubleTapListener
我們先來講講 OnDoubleTapListener,大家可能要問:剛剛不是已經(jīng)講過雙擊事件監(jiān)聽了嗎,這里又來不是浪費時間?廢話不說,讓我詳細介紹下這類的方法:
單擊回調(diào) SingleTapConfirmed
有人就會很好奇,對于單擊事件的回調(diào),直接去用 onClickListener 不就好了么,干嘛要用 SingleTapConfirmed 呢?
首先,這兩個方法是沖突的,這里就涉及到了事件分發(fā)機制,具體的后面有空再總結(jié)下,這里就不詳解了。
其二,更具備 onClickListener 的機制,我們不難發(fā)現(xiàn),如果是用 onClickListener 的話,當我們雙擊時,我們也會調(diào)用單擊事件,也就是單擊了兩次,這明顯是不符合我們意圖的。那么該如何調(diào)用呢?如下:
final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
MyToast.makeToast(GestureDetectorActivity.this, "single click!");
return super.onSingleTapConfirmed(e);
}
...
};
DoubleTap 與 onDoubleTapEvent
打算把這兩個方法放在一起將,一則他兩都屬于雙擊的范疇,二則他兩有著極高相似和細微卻重要的區(qū)別。
大家可以嘗試著在 onDoubleTapEvent和 DoubleTap 中,對點擊的 Down move 和 up 進行打印,你就會發(fā)現(xiàn),對于 DoubleTap 而言,它是在第二次點擊按下時,發(fā)生的回調(diào),而對于 onDoubleTapEvent 而言,則是在第二次點擊后,手指抬起離開了屏幕時,發(fā)生的回調(diào)。這就是他兩最重要的區(qū)別。
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的學習。
OnGestureListener
這可以說是整個手勢監(jiān)測中,最核心的部分了,前面都是引入,現(xiàn)在才是正題,這里我主要向大家介紹一下手勢:
- 按下(Down)
- 一扔(Fling)
- 長按(LongPress)
- 滾動(Scroll)
- 觸摸反饋(ShowPress)
- 單擊抬起(SingleTapUp)
onDown
onDown 事件很好理解,他在一個 View 被按下時執(zhí)行。也正是如此,要想能執(zhí)行 onDown ,首先要保證這個 View 是可以點擊的,也就是 onClickable 的值為 true 。
private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onDown(MotionEvent e) {
MyToast.makeToast(GestureDetectorActivity.this, "onDown");
// 后續(xù)事件
return super.onDown(e);
}
};
onFling
對于 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);
}
};
從代碼中,我們不難發(fā)現(xiàn):該方法有四個參數(shù)
| 參數(shù) | 意義 |
|---|---|
| e1 | 手指按下時的 Event。 |
| e2 | 手指抬起時的 Event。 |
| velocityX | 在 X 軸上的運動速度(像素/秒)。 |
| velocityY | 在 Y 軸上的運動速度(像素/秒)。 |
通過前兩個 MotionEvent 參數(shù),我們可以獲得點擊發(fā)生的位置等,通過后兩個 float 參數(shù),我們可以獲得手指滑動的速度。
具體使用其實還是蠻多的,比如我們可以想象下臺球游戲,球桿擊球后,就有這樣一個初速度遞減的效果。
onLongPress
onLongPress 很簡單,就是長按事件的回調(diào),比如說長按復制,長按彈窗等等,它不但應用廣泛,同時使用也非常簡單,這里就不嘮叨了
private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){
@Override
public void onLongPress(MotionEvent e) {
MyToast.makeToast(GestureDetectorActivity.this, "onLongPress");
// 后續(xù)工作
super.onLongPress(e);
}
};
onScroll
onScroll 方法和 onFling 很像,唯一的區(qū)別在于,onFling 的參數(shù)是滑動的速度,而 onScroll 的后兩個參數(shù)則是滑動的距離:
| 參數(shù) | 意義 |
|---|---|
| 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);
}
};
onShowPress
這個方法我其實覺得作用不是很大,因為它是在 View 被點擊(按下)是調(diào)用,其作用是給用戶一個視覺反饋,讓用戶知道我這個控件被點擊了,這樣的效果我們完全可以用 Material design 的 ripple 實現(xiàn),或者直接 drawable 寫個背景也行。
如果說它有什么特別指出的話,它是一種延時回調(diào),延遲時間是 180 ms。也就是說用戶手指按下后,如果立即抬起或者事件立即被攔截,時間沒有超過 180 ms的話,這條消息會被 remove 掉,也就不會觸發(fā)這個回調(diào)。
private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){
@Override
public void onShowPress(MotionEvent e) {
MyToast.makeToast(GestureDetectorActivity.this, "onShowPress");// >150ms 時調(diào)用
super.onShowPress(e);
}
};
onSingleTapUp
對于 onSingleTapUp 網(wǎng)上有很多分析,但我覺得過于復雜了,其實這東西很簡單。舉個例子你就懂了:
之前我們講過雙擊事件,那好 onSingleTapUp 就是在 雙擊事件的第一次點擊時回調(diào)。也就是說但你點擊了一個控件時(雙擊第一下),這個回調(diào)馬上會被調(diào)用,然后迅速點第二下(雙擊事件的第二下),則其不會被調(diào)用。
| 類型 | 觸發(fā)次數(shù) | 摘要 |
|---|---|---|
| onSingleTapUp | 1 | 在雙擊的第一次抬起時觸發(fā) |
| onSingleTapConfirmed | 0 | 雙擊發(fā)生時不會觸發(fā)。 |
| onClick | 2 | 在雙擊事件時觸發(fā)兩次。 |
它和 onSingleTapConfirmed 的區(qū)別也就很明顯了,onSingleTapConfirmed 在發(fā)生雙擊時,不會回調(diào),而 onSingleTapUp 只會在雙擊的的第一次回調(diào)。
private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onSingleTapUp(MotionEvent e) {// 雙擊第一次抬起觸發(fā),第二次不觸發(fā)
Log.d("onSingleTapUp", "onSingleTapUp");// >150ms 時調(diào)用
return super.onSingleTapUp(e);
}
};
SimpleOnGestureListener
SimpleOnGestureListener 中包含了以上所有方法的空實現(xiàn),之所以在文末再一次提及他,主要是想講下它的方便之處。
我們以監(jiān)聽 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;
}
});
我們不難發(fā)現(xiàn)一個問題,既然在 GestureDetector 實例化時,已經(jīng)實例化了一個 SimpleOnGestureListener 了,那么在舍近求遠的去使用 OnGestureListener 的話,會多出幾個無用的空實現(xiàn),顯然很浪費,所以在一般情況下,乖乖的使用 SimpleOnGestureListener 就好了。
其它
Android 除了提供了一個 GestureDetector 來幫助我們識別一些基本的觸摸手勢外,還有 ScaleGestureDetector 可以識別縮放手勢,讓我們很方便地實現(xiàn)手勢控制功能。
//-----------------------implement OnScaleGestureListener's method----------------------//
@Override
public boolean onScale(ScaleGestureDetector detector) {
Toast.makeText(MainActivity.this, "onScale", Toast.LENGTH_SHORT).show();
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
Toast.makeText(MainActivity.this, "onScaleBegin", Toast.LENGTH_SHORT).show();
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
Toast.makeText(MainActivity.this, "onScaleEnd", Toast.LENGTH_SHORT).show();
}
到此這篇關(guān)于Android點擊事件之多點觸摸與手勢識別的實現(xiàn)的文章就介紹到這了,更多相關(guān)Android 多點觸摸與手勢識別內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android使用ViewDragHelper實現(xiàn)圖片下拽返回示例
這篇文章主要介紹了Android使用ViewDragHelper實現(xiàn)圖片下拽返回示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08
Android編程實現(xiàn)帶有圖標的ListView并帶有長按菜單效果示例
這篇文章主要介紹了Android編程實現(xiàn)帶有圖標的ListView并帶有長按菜單效果,結(jié)合實例形式分析了Android帶圖標的ListView及菜單功能相關(guān)實現(xiàn)技巧,需要的朋友可以參考下2017-06-06
Android 手機衛(wèi)士實現(xiàn)平移動畫示例
這篇文章主要介紹了Android 手機衛(wèi)士實現(xiàn)平移動畫的實例代碼,本文介紹的非常詳細,具有參考借鑒價值,需要的朋友可以參考下2016-10-10
Android 中TextView中跑馬燈效果的實現(xiàn)方法
這篇文章主要介紹了Android 中TextView中跑馬燈效果的實現(xiàn)方法,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-02-02
Android多線程+單線程+斷點續(xù)傳+進度條顯示下載功能
這篇文章主要介紹了Android多線程+單線程+斷點續(xù)傳+進度條顯示下載功能,需要的朋友可以參考下2017-06-06

