Android點(diǎn)擊事件之多點(diǎn)觸摸與手勢(shì)識(shí)別的實(shí)現(xiàn)
最近遇到想要實(shí)現(xiàn)三指滑動(dòng)監(jiān)聽(tīng)的需求,實(shí)現(xiàn)代碼不方便貼出來(lái),但是思路還是可以記錄一下。
Muilti-touch 雙指縮放探索首先要實(shí)現(xiàn)OnTouchListener接口,然后重寫(xiě)方法:
public boolean onTouch(View v, MotionEvent event);
從這個(gè)方法中我們就可以獲取實(shí)現(xiàn)兩指縮放功能的全部信息。
View v是觸發(fā)事件的源,MotionEvent event即一個(gè)觸摸事件。對(duì)屏幕的幾乎所有操作都會(huì)觸發(fā)事件,如點(diǎn)擊、放開(kāi)、滑動(dòng)等。
不同的事件在MotionEvent中有不同的id,我們可以根據(jù)event.getAction() & MotionEvent.ACTION_MASK的結(jié)果來(lái)判斷是何種事件。
有如下事件使我們要用到的:
MotionEvent.ACTION_DOWN:在第一個(gè)點(diǎn)被按下時(shí)觸發(fā) MotionEvent.ACTION_UP:當(dāng)屏幕上唯一的點(diǎn)被放開(kāi)時(shí)觸發(fā) MotionEvent.ACTION_POINTER_DOWN:當(dāng)屏幕上已經(jīng)有一個(gè)點(diǎn)被按住,此時(shí)再按下其他點(diǎn)時(shí)觸發(fā)。 MotionEvent.ACTION_POINTER_UP:當(dāng)屏幕上有多個(gè)點(diǎn)被按住,松開(kāi)其中一個(gè)點(diǎn)時(shí)觸發(fā)(即非最后一個(gè)點(diǎn)被放開(kāi)時(shí))。 MotionEvent.ACTION_MOVE:當(dāng)有點(diǎn)在屏幕上移動(dòng)時(shí)觸發(fā)。值得注意的是,由于它的靈敏度很高,而我們的手指又不可能完全靜止(即使我們感覺(jué)不到移動(dòng),但其實(shí)我們的手指也在不停地抖動(dòng)),所以實(shí)際的情況是,基本上只要有點(diǎn)在屏幕上,此事件就會(huì)一直不停地被觸發(fā)。舉例子來(lái)說(shuō):當(dāng)我們放一個(gè)食指到屏幕上時(shí),觸發(fā)ACTION_DOWN事件;再放一個(gè)中指到屏幕上,觸發(fā)ACTION_POINTER_DOWN事件;此時(shí)再把食指或中指放開(kāi),都會(huì)觸發(fā)ACTION_POINTER_UP事件;再放開(kāi)最后一個(gè)手指,觸發(fā)ACTION_UP事件;而同時(shí)在整個(gè)過(guò)程中,ACTION_MOVE事件會(huì)一直不停地被觸發(fā)。
event.getX(index)和event.getY(index)可以獲取到指定index點(diǎn)的坐標(biāo),所以當(dāng)屏幕上有兩個(gè)點(diǎn)的時(shí)候,我們用如下方法來(lái)獲取兩點(diǎn)間的距離:
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ā)的不同事件來(lái)判斷當(dāng)前屏幕上的點(diǎn)的個(gè)數(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事件中,判斷點(diǎn)的個(gè)數(shù),如果大于等于2,就計(jì)算兩點(diǎn)間的距離,如果距離增大就把圖片放大,距離減少就把圖片縮小。
于是代碼就成了:
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);//兩點(diǎn)按下時(shí)的距離 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)過(guò)檢驗(yàn),這種方法是能夠?qū)崿F(xiàn)縮放效果的。
但是有了另外一個(gè)問(wèn)題:就是由于ACTION_MOVE會(huì)因顫抖一直被觸發(fā),而每次觸發(fā)的時(shí)候兩點(diǎn)間的距離也總會(huì)有細(xì)小的變化,所以運(yùn)行之后只要有兩點(diǎn)在屏幕上,就總會(huì)在放大或縮小字體。
經(jīng)過(guò)一番思考,我想出了一個(gè)控制其靈敏度的方法,即在case MotionEvent.ACTION_MOVE時(shí)判斷只有當(dāng)距離變化大于一定程度時(shí)才會(huì)更改字體大小:
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); } }
這樣,基本算是能達(dá)到預(yù)期的效果了。
Android原生帶的手勢(shì)監(jiān)聽(tīng)GestureDetector 使用GestureDetector 是 Android 中,專門用來(lái)進(jìn)行手勢(shì)監(jiān)聽(tīng)的一個(gè)對(duì)象,在他的監(jiān)聽(tīng)器中,我們通過(guò)傳入 MotionEvents 對(duì)象,就可以在各種事件的回調(diào)方法中各種手勢(shì)進(jìn)行監(jiān)測(cè)。舉個(gè)例子: GestureDetector 的 OnGestureListener 就是一種回調(diào)方法,就是說(shuō)在獲得了傳入的這個(gè) MotionEvents 對(duì)象之后,進(jìn)行了處理,我們通過(guò)重寫(xiě)了其中的各種方法(單擊事件、雙擊事件等等),就可以監(jiān)聽(tīng)到單擊,雙擊,滑動(dòng)等事件,然后直接在這些方法內(nèi)部進(jìn)行處理。
使用方法
首先,創(chuàng)建一個(gè) SimpleOnGestureListener 回調(diào)方法對(duì)象,并對(duì)其中各個(gè)方法進(jìn)行重寫(xiě)根據(jù)這個(gè) listener 對(duì)象,實(shí)例化出 GestureDetector 對(duì)象對(duì)目標(biāo)控件重寫(xiě) setOnTouchListener 方法,并在其中調(diào)用 detector 對(duì)象的 onTouchEvent 方法即可簡(jiǎn)單易懂,一分鐘搞定。
@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 這個(gè)回調(diào)方法之外,還有 SingleTapConfirmed 和 DoubleTap 這兩個(gè)回調(diào)方法 OnGestureListener :這里集合了眾多手勢(shì)的監(jiān)聽(tīng)器:主要有:按下(Down)、 扔(Fling)、長(zhǎng)按(LongPress)、滾動(dòng)(Scroll)、觸摸反饋(ShowPress) 和 單擊抬起(SingleTapUp) SimpleOnGestureListener :上述接口的空實(shí)現(xiàn),用的頻率比較多OnDoubleTapListener
我們先來(lái)講講 OnDoubleTapListener,大家可能要問(wèn):剛剛不是已經(jīng)講過(guò)雙擊事件監(jiān)聽(tīng)了嗎,這里又來(lái)不是浪費(fèi)時(shí)間?廢話不說(shuō),讓我詳細(xì)介紹下這類的方法:
單擊回調(diào) SingleTapConfirmed
有人就會(huì)很好奇,對(duì)于單擊事件的回調(diào),直接去用 onClickListener 不就好了么,干嘛要用 SingleTapConfirmed 呢?
首先,這兩個(gè)方法是沖突的,這里就涉及到了事件分發(fā)機(jī)制,具體的后面有空再總結(jié)下,這里就不詳解了。
其二,更具備 onClickListener 的機(jī)制,我們不難發(fā)現(xiàn),如果是用 onClickListener 的話,當(dāng)我們雙擊時(shí),我們也會(huì)調(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
打算把這兩個(gè)方法放在一起將,一則他兩都屬于雙擊的范疇,二則他兩有著極高相似和細(xì)微卻重要的區(qū)別。
大家可以嘗試著在 onDoubleTapEvent和 DoubleTap 中,對(duì)點(diǎn)擊的 Down move 和 up 進(jìn)行打印,你就會(huì)發(fā)現(xiàn),對(duì)于 DoubleTap 而言,它是在第二次點(diǎn)擊按下時(shí),發(fā)生的回調(diào),而對(duì)于 onDoubleTapEvent 而言,則是在第二次點(diǎn)擊后,手指抬起離開(kāi)了屏幕時(shí),發(fā)生的回調(diào)。這就是他兩最重要的區(qū)別。
final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){ @Overridepublic boolean onDoubleTap(MotionEvent e) { MyToast.makeToast(GestureDetectorActivity.this, 'double click down!'); return super.onDoubleTap(e);} @Overridepublic boolean onDoubleTapEvent(MotionEvent e) { switch (e.getActionMasked()){case MotionEvent.ACTION_UP: MyToast.makeToast(GestureDetectorActivity.this, 'double click up!'); break; } return super.onDoubleTapEvent(e);} };
所以,有了這兩個(gè)方法,我們就可以更具目的性的滿足兩種需求。 到這里,單擊雙擊事件就告一段落了,下面我們進(jìn)入OnGestureListener的學(xué)習(xí)。
OnGestureListener
這可以說(shuō)是整個(gè)手勢(shì)監(jiān)測(cè)中,最核心的部分了,前面都是引入,現(xiàn)在才是正題,這里我主要向大家介紹一下手勢(shì):
按下(Down) 一扔(Fling) 長(zhǎng)按(LongPress) 滾動(dòng)(Scroll) 觸摸反饋(ShowPress) 單擊抬起(SingleTapUp)onDown
onDown 事件很好理解,他在一個(gè) View 被按下時(shí)執(zhí)行。也正是如此,要想能執(zhí)行 onDown ,首先要保證這個(gè) View 是可以點(diǎn)擊的,也就是 onClickable 的值為 true 。
private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){ @Overridepublic boolean onDown(MotionEvent e) { MyToast.makeToast(GestureDetectorActivity.this, 'onDown'); // 后續(xù)事件 return super.onDown(e);} };
onFling
對(duì)于 onFling 我個(gè)人感覺(jué)這是個(gè)最常用的方法,就像它的名字,翻譯過(guò)來(lái)是拖、拽、扔的意思。舉個(gè)例子 RecyclerView 或者 ListView 我們都有用過(guò),當(dāng)我們快速上拉后會(huì)滾動(dòng)一定距離停止,我們可愛(ài)的 onFling 就是用于檢測(cè)這種手勢(shì)的。
private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){@Overridepublic 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):該方法有四個(gè)參數(shù)
參數(shù) 意義 e1 手指按下時(shí)的 Event。 e2 手指抬起時(shí)的 Event。 velocityX 在 X 軸上的運(yùn)動(dòng)速度(像素/秒)。 velocityY 在 Y 軸上的運(yùn)動(dòng)速度(像素/秒)。
通過(guò)前兩個(gè) MotionEvent 參數(shù),我們可以獲得點(diǎn)擊發(fā)生的位置等,通過(guò)后兩個(gè) float 參數(shù),我們可以獲得手指滑動(dòng)的速度。
具體使用其實(shí)還是蠻多的,比如我們可以想象下臺(tái)球游戲,球桿擊球后,就有這樣一個(gè)初速度遞減的效果。
onLongPress
onLongPress 很簡(jiǎn)單,就是長(zhǎng)按事件的回調(diào),比如說(shuō)長(zhǎng)按復(fù)制,長(zhǎng)按彈窗等等,它不但應(yīng)用廣泛,同時(shí)使用也非常簡(jiǎn)單,這里就不嘮叨了
private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){ @Overridepublic void onLongPress(MotionEvent e) { MyToast.makeToast(GestureDetectorActivity.this, 'onLongPress'); // 后續(xù)工作 super.onLongPress(e);} };
onScroll
onScroll 方法和 onFling 很像,唯一的區(qū)別在于,onFling 的參數(shù)是滑動(dòng)的速度,而 onScroll 的后兩個(gè)參數(shù)則是滑動(dòng)的距離:
參數(shù) 意義 e1 手指按下時(shí)的 MotionEvent e2 手指抬起時(shí)的 MotionEvent distanceX 在 X 軸上劃過(guò)的距離 distanceY 在 Y 軸上劃過(guò)的距離
private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){@Overridepublic 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
這個(gè)方法我其實(shí)覺(jué)得作用不是很大,因?yàn)樗窃?View 被點(diǎn)擊(按下)是調(diào)用,其作用是給用戶一個(gè)視覺(jué)反饋,讓用戶知道我這個(gè)控件被點(diǎn)擊了,這樣的效果我們完全可以用 Material design 的 ripple 實(shí)現(xiàn),或者直接 drawable 寫(xiě)個(gè)背景也行。
如果說(shuō)它有什么特別指出的話,它是一種延時(shí)回調(diào),延遲時(shí)間是 180 ms。也就是說(shuō)用戶手指按下后,如果立即抬起或者事件立即被攔截,時(shí)間沒(méi)有超過(guò) 180 ms的話,這條消息會(huì)被 remove 掉,也就不會(huì)觸發(fā)這個(gè)回調(diào)。
private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){@Overridepublic void onShowPress(MotionEvent e) { MyToast.makeToast(GestureDetectorActivity.this, 'onShowPress');// >150ms 時(shí)調(diào)用 super.onShowPress(e);} };
onSingleTapUp
對(duì)于 onSingleTapUp 網(wǎng)上有很多分析,但我覺(jué)得過(guò)于復(fù)雜了,其實(shí)這東西很簡(jiǎn)單。舉個(gè)例子你就懂了:
之前我們講過(guò)雙擊事件,那好 onSingleTapUp 就是在 雙擊事件的第一次點(diǎn)擊時(shí)回調(diào)。也就是說(shuō)但你點(diǎn)擊了一個(gè)控件時(shí)(雙擊第一下),這個(gè)回調(diào)馬上會(huì)被調(diào)用,然后迅速點(diǎn)第二下(雙擊事件的第二下),則其不會(huì)被調(diào)用。
類型 觸發(fā)次數(shù) 摘要 onSingleTapUp 1 在雙擊的第一次抬起時(shí)觸發(fā) onSingleTapConfirmed 0 雙擊發(fā)生時(shí)不會(huì)觸發(fā)。 onClick 2 在雙擊事件時(shí)觸發(fā)兩次。
它和 onSingleTapConfirmed 的區(qū)別也就很明顯了,onSingleTapConfirmed 在發(fā)生雙擊時(shí),不會(huì)回調(diào),而 onSingleTapUp 只會(huì)在雙擊的的第一次回調(diào)。
private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){@Overridepublic boolean onSingleTapUp(MotionEvent e) {// 雙擊第一次抬起觸發(fā),第二次不觸發(fā) Log.d('onSingleTapUp', 'onSingleTapUp');// >150ms 時(shí)調(diào)用 return super.onSingleTapUp(e);} };
SimpleOnGestureListener
SimpleOnGestureListener 中包含了以上所有方法的空實(shí)現(xiàn),之所以在文末再一次提及他,主要是想講下它的方便之處。
我們以監(jiān)聽(tīng) OnDoubleTapListener 為例,如果想要使用 OnDoubleTapListener 接口則需要這樣進(jìn)行設(shè)置:
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)一個(gè)問(wèn)題,既然在 GestureDetector 實(shí)例化時(shí),已經(jīng)實(shí)例化了一個(gè) SimpleOnGestureListener 了,那么在舍近求遠(yuǎn)的去使用 OnGestureListener 的話,會(huì)多出幾個(gè)無(wú)用的空實(shí)現(xiàn),顯然很浪費(fèi),所以在一般情況下,乖乖的使用 SimpleOnGestureListener 就好了。
其它Android 除了提供了一個(gè) GestureDetector 來(lái)幫助我們識(shí)別一些基本的觸摸手勢(shì)外,還有 ScaleGestureDetector 可以識(shí)別縮放手勢(shì),讓我們很方便地實(shí)現(xiàn)手勢(shì)控制功能。
//-----------------------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點(diǎn)擊事件之多點(diǎn)觸摸與手勢(shì)識(shí)別的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Android 多點(diǎn)觸摸與手勢(shì)識(shí)別內(nèi)容請(qǐng)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!
相關(guān)文章:
1. Python2.6版本pip安裝步驟解析2. python公司內(nèi)項(xiàng)目對(duì)接釘釘審批流程的實(shí)現(xiàn)3. python中Ansible模塊的Playbook的具體使用4. Python自動(dòng)化之定位方法大殺器xpath5. Python本地及虛擬解釋器配置過(guò)程解析6. Python 利用flask搭建一個(gè)共享服務(wù)器的步驟7. 基于python實(shí)現(xiàn)matlab filter函數(shù)過(guò)程詳解8. Python中Anaconda3 安裝gdal庫(kù)的方法9. python自動(dòng)化測(cè)試三部曲之request+django實(shí)現(xiàn)接口測(cè)試10. Python importlib模塊重載使用方法詳解
