【本文出自大圣代的技術(shù)專欄 http://blog.csdn.net/qq_23191031】
【轉(zhuǎn)載煩請(qǐng)注明出處先紫,尊重他人勞動(dòng)成果就是對(duì)您自己的尊重】
前言
在《【Android 控件架構(gòu)】詳解Android控件架構(gòu)與常用坐標(biāo)系》這篇文章中,我曾在【常用坐標(biāo)系】一節(jié)中簡(jiǎn)單描述過 MotionEvent 常用方法筹煮,鑒于最近工作中大量處理了View觸摸事件遮精,特此展開討論。
1败潦,MotionEvent
對(duì)于 MotionEvent 官網(wǎng)是如此定義的:
Motion events describe movements in terms of an action code and a set of axis values. The action code specifies the state change that occurred such as a pointer going down or up. The axis values describe the position and other movement properties.
譯:
運(yùn)動(dòng)事件描述了動(dòng)作的動(dòng)作代碼和一些列的坐標(biāo)值本冲。動(dòng)作代碼表明了當(dāng)觸點(diǎn)按下或者彈起等引起的狀態(tài)變化。坐標(biāo)值描述了位置信息以及以他的運(yùn)動(dòng)屬性劫扒。
官網(wǎng)對(duì)于MotionEvent的描述相當(dāng)準(zhǔn)確檬洞,但是過于抽象,不要緊沟饥,請(qǐng)隨我來......
1.1 獲取點(diǎn)擊事件具體坐標(biāo)
我在《【Android 控件架構(gòu)】詳解Android控件架構(gòu)與常用坐標(biāo)系》第二節(jié)【視圖坐標(biāo)系】中有詳細(xì)描述添怔,這里不再贅述。
貼一張圖贤旷,答案盡在不言中广料。
1.2 事件類型
從源碼中可以看到,MotionEvent封裝了如下事件類型遮晚。
public static final int ACTION_DOWN = 0;
public static final int ACTION_UP = 1;
public static final int ACTION_MOVE = 2;
public static final int ACTION_CANCEL = 3;
public static final int ACTION_OUTSIDE = 4;
public static final int ACTION_POINTER_DOWN = 5;
public static final int ACTION_POINTER_UP = 6;
類型 | 說明 |
---|---|
ACTION_DOWN | 標(biāo)志著第一個(gè)手指按下 |
ACTION_UP | 最后一個(gè)手指抬起 |
ACTION_MOVE | 按住一點(diǎn)手指開始移動(dòng) |
ACTION_CANCEL | 表示手勢(shì)被取消了性昭,不再接受后續(xù)事件 |
ACTION_OUTSIDE | 標(biāo)志著用戶觸碰到了正常的UI邊界 |
ACTION_POINTER_DOWN | 代表用戶又使用一個(gè)手指觸摸到屏幕上,也就是說县遣,在已經(jīng)有一個(gè)觸摸點(diǎn)的情況下糜颠,又新出現(xiàn)了一個(gè)觸摸點(diǎn)汹族。 |
ACTION_POINTER_UP | 非最后一個(gè)手指抬起 |
對(duì)于ACTION_CANCEL我要特殊說明一下,要很好的理解它就不得不說到 ViewGroup的事件分發(fā)機(jī)制了其兴。一般來說顶瞒,一個(gè)View接收了其父View/ViewGroup分發(fā)而來的ACTION_DOWN事件,那么接下來與ACTION_DOWN相關(guān)的事件流都會(huì)分發(fā)給此View處理元旬,但是如果對(duì)于某一個(gè)事件榴徐,其父View/ViewGroup想要攔截并自行處理,那么父View/ViewGroup就會(huì)給子視圖一個(gè)ACTION_CANCEL事件匀归。對(duì)于事件分發(fā)的內(nèi)容我會(huì)在以后的文章中作出詳細(xì)解答坑资。這里先留一個(gè)懸念青责。
多指手勢(shì)操作和Pointers多點(diǎn)觸摸時(shí)括眠,我們?cè)趺粗肋@個(gè)一個(gè)MotionEvent是哪一個(gè)觸控點(diǎn)觸發(fā)的呢,所以Android就引入了 pointers 的概念迄沫,多點(diǎn)觸控時(shí)每一個(gè)觸控點(diǎn)都會(huì)產(chǎn)生一個(gè)運(yùn)動(dòng)軌跡体啰,而引發(fā)這個(gè)運(yùn)動(dòng)軌跡的手指或者物體(如觸控筆等)被稱為 pointers攒巍。
關(guān)于Pointers的一些特性總結(jié)
- 一個(gè)MotionEtvent對(duì)象中可存儲(chǔ)多個(gè) pointer
- 每個(gè)pointer都有自己的事件類型,也有自己的坐標(biāo)值荒勇。
- 每個(gè)pointer都會(huì)有一個(gè)自己的id和index(下面的內(nèi)容會(huì)提到)
- pointer的id在整個(gè)事件流中是不會(huì)發(fā)生變化的柒莉,但是index會(huì)發(fā)生變化。所以沽翔,當(dāng)我們要記錄一個(gè)觸摸點(diǎn)的事件流時(shí)兢孝,就只需要保存其id,然后使用findPointerIndex(int)來獲得其index值,然后再獲得其他信息搀擂。
- MotionEvent類中的很多方法都是可以傳入一個(gè)int值作為參數(shù)的西潘,其實(shí)傳入的就是pointer的index值。比如getX(pointerIndex)和getY(pointerIndex)哨颂,此時(shí),它們返回的就是index所代表的觸摸點(diǎn)相關(guān)事件坐標(biāo)值相种。
案例講解
用戶先兩個(gè)手指先后接觸屏幕威恼,同時(shí)滑動(dòng),然后在先后離開寝并。這一套動(dòng)作所產(chǎn)生的事件流是什么樣的呢箫措??衬潦?
常用事件
與單點(diǎn)觸發(fā)一樣斤蔓,多點(diǎn)觸發(fā)的相關(guān)信息也存儲(chǔ)在 onTouchEvent方法的MotinoEvent中:
int getPointerCount() //手勢(shì)操作所包含的點(diǎn)的個(gè)數(shù)
int findPointerIndex(int pointerId) //根據(jù)pointerId找到pointer在MotionEvent中的index
int getPointerId(int pointerIndex) //根據(jù)MotionEvent中的index返回pointer的唯一標(biāo)識(shí)
float getX(int pointerIndex) //返回手勢(shì)操作點(diǎn)的x坐標(biāo)
float getY(int pointerIndex) //返回手勢(shì)操作點(diǎn)的y坐標(biāo)
final int getActionMasked () //獲取特殊點(diǎn)的action
/*
* 用來獲取當(dāng)前按下/抬起的點(diǎn)的標(biāo)識(shí),
* 如果當(dāng)前沒有任何點(diǎn)抬起/按下,該函數(shù)返回0镀岛。比如事件類型為ACTION_MOVE時(shí)弦牡,該值始終為 0友驮。
*/
final int getActionIndex()
獲取事件類型 getAction 和 getActionMasked
先貼上一段源碼:
/**
* action碼的位掩碼部分就是action本身
*/
public static final int ACTION_MASK = 0xff;
/**
* 返回action的類型,考慮使用getActionMasked()和getActionIndex()來獲得單獨(dú)的經(jīng)過掩碼的action和觸控點(diǎn)的索引.
* @return action例如ACTION_DOWN或者ACTION_POINTER_DOWN與轉(zhuǎn)換的觸控點(diǎn)索引的合成值
*/
public final int getAction() {
return nativeGetAction(mNativePtr);
}
/**
* 返回經(jīng)過掩碼的action,沒有觸控點(diǎn)索引信息.
* 通過getActionIndex()來得到觸控操作點(diǎn)的索引.
* @return action,例如ACTION_DOWN,ACTION_POINTER_DOWN
*/
public final int getActionMasked() {
return nativeGetAction(mNativePtr) & ACTION_MASK;
}
查看源碼發(fā)現(xiàn)getAction
返回的是一個(gè)整數(shù),這就說明了Android系統(tǒng)實(shí)質(zhì)上使用一個(gè)32位的整形數(shù)表示一個(gè)TouchEvent事件驾锰,而這兩個(gè)方法的差異就在于 getActionMasked()
返回的是 getAction()
和 ACTION_MASK
進(jìn)行&
(按位與)操作后的結(jié)果卸留。
ACTION_MASK
十進(jìn)制為 255
到這里我們可以得知:
getActionMasked()
返回的是getAction()
低8位內(nèi)容。getAction()
的低8才是動(dòng)作碼椭豫,也就是說getActionMasked()
返回的僅僅是動(dòng)作碼
ACTION_POINTER_INDEX_MASK
十進(jìn)制為: 65280 (0x0000ff00)
public static final int ACTION_POINTER_INDEX_MASK = 0xff00;
public static final int ACTION_POINTER_INDEX_SHIFT = 8;
public final int getActionIndex() {
return (nativeGetAction(mNativePtr) & ACTION_POINTER_INDEX_MASK)
>> ACTION_POINTER_INDEX_SHIFT;
}
到這里我們可以得知:
getAction()
的相對(duì)高8位耻瑟,就是用于區(qū)分出發(fā)事件順序的 index
小結(jié)
事件或方法 | 描述 |
---|---|
getAction() |
觸摸動(dòng)作的原始32位信息,包括事件的動(dòng)作赏酥,觸控點(diǎn)信息喳整。32位中只有后16位被使用,其中相對(duì)高8位觸控點(diǎn)信息裸扶,低8位為事件動(dòng)作 |
getActionMask() |
只包含觸摸的動(dòng)作,如按下算柳,抬起,滑動(dòng)姓言,多點(diǎn)按下瞬项,多點(diǎn)抬起,如果想處理多點(diǎn)觸摸炫需要使用 getActionMask() 與MotionEvent中的ACTION_POINTER_DOWN 何荚、ACTION_POINTER_UP 囱淋、ACTION_POINTER_1_DOWN等比對(duì)判斷 |
單點(diǎn)觸控 |
getAction() 和getActionMasked() 返回的是值一樣的,都只包含事件動(dòng)作碼 |
多點(diǎn)觸控 |
getAction() 和getActionMasked() 返回值不同,getAction() 比getActionMasked() 多了觸控點(diǎn)索引 |
總而言之:
對(duì)于單點(diǎn)觸摸,使用這倆個(gè)方法誰都行餐塘,但是對(duì)于多點(diǎn)觸控妥衣,推進(jìn)getActionMask方法,可以直接獲取ACTION_POINTER_DOWN等多點(diǎn)觸控狀態(tài)碼戒傻。
為啥說推動(dòng)使用 getActionMask 呢税手,因?yàn)間etAction包含了全部信息,例如雙指按下需纳,如果使用getAction的話芦倒,這個(gè)值是261,如果使用getActionMasked這個(gè)值是5(ACTION_POINTER_DOWN =5)不翩,所以不閑麻煩兵扬,你完全可以使用 getAction方法 通過261硬編碼的方式判斷。
提問時(shí)間:
為什么Android不用兩個(gè)字段表示呢口蝠?例如 mAction,mPointer mAction表示動(dòng)作類型器钟,mPointer表示第幾個(gè)觸控點(diǎn)。
答: 因?yàn)锳ction與Pointer都只需要256(0~255)個(gè)數(shù)就可以表示全妙蔗,只要一個(gè)int
字段(32位)傲霸,否則需要兩個(gè)字段(32*2=64位),即可以節(jié)約內(nèi)存。又可以方便提高處理速度昙啄。不過通常我們都是以不同的字段來存儲(chǔ)不同的信息穆役。但是在計(jì)算機(jī)內(nèi)部他們還是變成了0,1。計(jì)算機(jī)始終還是以位來存儲(chǔ)信息的跟衅。如果我們多熟悉以 位 為基本單位來理解信息的存儲(chǔ)孵睬。對(duì)于理解android中的很多變量是很有幫助的。因?yàn)樗渲械暮芏鄸|西使用的這樣的節(jié)約內(nèi)在的技巧伶跷。如onMeasure中的MeasureSpec掰读,Color.argb()等方法也是如此。
其他
Android 將所有的輸入事件都放在了 MotionEvent 中叭莫,隨著安卓的不斷發(fā)展壯大蹈集,MotionEvent 也開始變得越來越復(fù)雜,下面是我自己整理的 MotionEvent 大事記:
2雇初,ViewConfiguration
在ViewConfiguration這個(gè)類中拢肆,主要定義了UI中所有使用到的標(biāo)準(zhǔn)常量,像超時(shí)靖诗、尺寸郭怪、距離,如果我們需要得到這些常量的數(shù)據(jù)刊橘,我們就可以通過這個(gè)類來獲取鄙才。
注意:
獲取ViewConfiguration對(duì)象時(shí),由于ViewConfiguration的構(gòu)造方法為私有的促绵,只能通過這個(gè)靜態(tài)方法來獲取到該對(duì)象攒庵。
ViewConfiguration configure = ViewConfiguration.get(context);
2.1,TouchSlop
TouchSlop是處理觸摸事件中的一個(gè)常量败晴,被系統(tǒng)認(rèn)為滑動(dòng)和點(diǎn)擊事件的臨界點(diǎn)浓冒。理解這個(gè)touchSlop是一個(gè)滑動(dòng)距離值的常量,也就是說當(dāng)我們手觸摸在屏幕上滑動(dòng)時(shí)尖坤,如果滑動(dòng)距離沒有超過touchSlop值的話 稳懒,android系統(tǒng)本身是不會(huì)認(rèn)為我們?cè)谄聊簧献隽耸謩?shì)滑動(dòng),因此只有當(dāng)我們?cè)谄聊簧系幕瑒?dòng)距離超過touchSlop值時(shí)糖驴,android系統(tǒng)本身才會(huì)認(rèn)為我們做了滑動(dòng)操作并去響應(yīng)觸摸事件僚祷,不過要注意的是不同的設(shè)備,touchSlop的值可能是不同的贮缕,一切以函數(shù)獲取為準(zhǔn)。它在各家手機(jī)系統(tǒng)默認(rèn)值是不同的俺榆。
在使用時(shí)我們可以通過:ViewConfiguration.get(getContext()).getScaledTouchSlop()
獲取系統(tǒng)的滑動(dòng)常量來感昼,判斷此時(shí)是否屬于滑動(dòng)事件;
其中ViewConfiguration這個(gè)類主要定義了UI中所使用到的標(biāo)準(zhǔn)常量,像超時(shí)罐脊、尺寸定嗓、距離蜕琴。
在處理滑動(dòng)事件時(shí),其實(shí)可以利用這個(gè)值來過濾掉一些沒必要的動(dòng)作宵溅,比如當(dāng)兩次滑動(dòng)距離小于這個(gè)值時(shí)凌简,我們就可以認(rèn)為滑動(dòng)沒發(fā)生,從而更好的優(yōu)化用戶體驗(yàn)恃逻。
附上一段常用源碼
/**
* 包含了方法和標(biāo)準(zhǔn)的常量用來設(shè)置UI的超時(shí)雏搂、大小和距離
*/
public class ViewConfiguration {
// 設(shè)定水平滾動(dòng)條的寬度和垂直滾動(dòng)條的高度,單位是像素px
private static final int SCROLL_BAR_SIZE = 10;
//定義滾動(dòng)條逐漸消失的時(shí)間寇损,單位是毫秒
private static final int SCROLL_BAR_FADE_DURATION = 250;
// 默認(rèn)的滾動(dòng)條多少秒之后消失凸郑,單位是毫秒
private static final int SCROLL_BAR_DEFAULT_DELAY = 300;
// 定義邊緣地方褪色的長(zhǎng)度
private static final int FADING_EDGE_LENGTH = 12;
//定義子控件按下狀態(tài)的持續(xù)事件
private static final int PRESSED_STATE_DURATION = 125;
//定義一個(gè)按下狀態(tài)轉(zhuǎn)變成長(zhǎng)按狀態(tài)的轉(zhuǎn)變時(shí)間
private static final int LONG_PRESS_TIMEOUT = 500;
//定義用戶在按住適當(dāng)按鈕,彈出全局的對(duì)話框的持續(xù)時(shí)間
private static final int GLOBAL_ACTIONS_KEY_TIMEOUT = 500;
//定義一個(gè)touch事件中是點(diǎn)擊事件還是一個(gè)滑動(dòng)事件所需的時(shí)間矛市,如果用戶在這個(gè)時(shí)間之內(nèi)滑動(dòng)芙沥,那么就認(rèn)為是一個(gè)點(diǎn)擊事件
private static final int TAP_TIMEOUT = 115;
/**
* Defines the duration in milliseconds we will wait to see if a touch event
* is a jump tap. If the user does not complete the jump tap within this interval, it is
* considered to be a tap.
*/
//定義一個(gè)touch事件時(shí)候是一個(gè)點(diǎn)擊事件。如果用戶在這個(gè)時(shí)間內(nèi)沒有完成這個(gè)點(diǎn)擊浊吏,那么就認(rèn)為是一個(gè)點(diǎn)擊事件
private static final int JUMP_TAP_TIMEOUT = 500;
//定義雙擊事件的間隔時(shí)間
private static final int DOUBLE_TAP_TIMEOUT = 300;
//定義一個(gè)縮放控制反饋到用戶界面的時(shí)間
private static final int ZOOM_CONTROLS_TIMEOUT = 3000;
/**
* Inset in pixels to look for touchable content when the user touches the edge of the screen
*/
private static final int EDGE_SLOP = 12;
/**
* Distance a touch can wander before we think the user is scrolling in pixels
*/
private static final int TOUCH_SLOP = 16;
/**
* Distance a touch can wander before we think the user is attempting a paged scroll
* (in dips)
*/
private static final int PAGING_TOUCH_SLOP = TOUCH_SLOP * 2;
/**
* Distance between the first touch and second touch to still be considered a double tap
*/
private static final int DOUBLE_TAP_SLOP = 100;
/**
* Distance a touch needs to be outside of a window's bounds for it to
* count as outside for purposes of dismissing the window.
*/
private static final int WINDOW_TOUCH_SLOP = 16;
//用來初始化fling的最小速度而昨,單位是每秒多少像素
private static final int MINIMUM_FLING_VELOCITY = 50;
//用來初始化fling的最大速度,單位是每秒多少像素
private static final int MAXIMUM_FLING_VELOCITY = 4000;
//視圖繪圖緩存的最大尺寸找田,以字節(jié)表示歌憨。在ARGB888格式下,這個(gè)尺寸應(yīng)至少等于屏幕的大小
@Deprecated
private static final int MAXIMUM_DRAWING_CACHE_SIZE = 320 * 480 * 4; // HVGA screen, ARGB8888
//flings和scrolls摩擦力度大小的系數(shù)
private static float SCROLL_FRICTION = 0.015f;
/**
* Max distance to over scroll for edge effects
*/
private static final int OVERSCROLL_DISTANCE = 0;
/**
* Max distance to over fling for edge effects
*/
private static final int OVERFLING_DISTANCE = 4;
}
3午阵, VelocityTracker(讀音 [v??lɑ:s?ti] ['tr?k?r] )
VelocityTracker是個(gè)速度跟蹤類躺孝,用于跟蹤手指滑動(dòng)的速度,包括x軸方向和y軸方向的速度底桂。如快速滑動(dòng)或者其他手勢(shì)操作植袍。當(dāng)我們準(zhǔn)備開始跟蹤滑動(dòng)速率時(shí)可以使用obtain()方法來獲取一個(gè) VelocityTracker的實(shí)例,然后在onTouchEvent回調(diào)函數(shù)中籽懦,使用addMovement(MotionEvent)函數(shù)將當(dāng)前的 移動(dòng)事件傳遞給VelocityTracker對(duì)象于个。當(dāng)我們決定計(jì)算當(dāng)前觸摸點(diǎn)的速率時(shí)可以調(diào)用computeCurrentVelocity(int units)函數(shù)來計(jì)算當(dāng)前的速度,使用getXVelocity() 暮顺、getYVelocity()函數(shù)來獲得當(dāng)前X軸和Y軸的速度厅篓。
使用實(shí)例
@Override
public boolean onTouchEvent(MotionEvent event) {
LogUtils.e("onTouchEvent start!!");
Log.i(TAG, "ACTION_DOWN");
if(null == mVelocityTracker) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
final VelocityTracker verTracker = mVelocityTracker;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//獲取第一個(gè)觸點(diǎn)的id, 此時(shí)可能有多個(gè)觸點(diǎn)捶码,獲取其中一個(gè)
mPointerId = event.getPointerId(0);
break;
case MotionEvent.ACTION_MOVE:
//計(jì)算瞬時(shí)速度
verTracker.computeCurrentVelocity(1000, mMaxVelocity);
float velocityX = verTracker.getXVelocity(mPointerId);
float velocityY = verTracker.getYVelocity(mPointerId);
LogUtils.e("velocityX-->" + velocityX);
LogUtils.e("velocityY-->"+velocityY);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
releaseVelocityTracker();//釋放資源
break;
default:
break;
}
return super.onTouchEvent(event);
}
/**
* 使用完VelocityTracker羽氮,必須釋放資源
*/
private void releaseVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.clear();
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
4,鳴謝:
如果說我比別人看得更遠(yuǎn)些,那是因?yàn)槲艺驹诹司奕说募缟?/p>
android觸控,先了解MotionEvent(一)
Android觸摸事件--MotionEvent
ViewConfiguration解析
【大圣代的技術(shù)專欄 http://blog.csdn.net/qq_23191031 轉(zhuǎn)載煩請(qǐng)注明出處惫恼,尊重他人勞動(dòng)成功就是對(duì)您自己的尊重】