前言
之前寫(xiě)了一篇文章是關(guān)于自定義控件的。在學(xué)習(xí)自定義view的時(shí)候順便把安卓的touch system(安卓觸摸機(jī)制)也給系統(tǒng)學(xué)習(xí)了一遍。學(xué)習(xí)過(guò)程就是看了一個(gè)老外兩個(gè)多小時(shí)的視頻,是一位叫Dave Smith的大牛(安卓系統(tǒng)開(kāi)發(fā)工程師)。不得不說(shuō),最新最好的東西來(lái)自國(guó)外。這篇文章算我對(duì)兩個(gè)月前看這個(gè)視頻之后的總結(jié)吧。
Android如何處理觸摸事件
MotionEvent
我相信MotionEvent這個(gè)sdk提供的常量大家應(yīng)該不陌生吧。這是android系統(tǒng)給我們封裝好的用來(lái)存儲(chǔ)touch事件的東西。它包含了很多你感興趣的東西。比如觸摸的時(shí)間,觸摸的手指數(shù),觸摸的地點(diǎn)等等。。。而它又分為action_down, action_up等等很多類型。開(kāi)發(fā)者可以通過(guò)這些名字來(lái)分別觸摸事件的類別,我相信大家也用到過(guò)這些。
- action_down 所有的觸摸事件開(kāi)始于這個(gè)。也就是你的手指觸摸到屏幕的時(shí)候產(chǎn)生的事件。
- action_up 所有的觸摸事件終止于這個(gè)。也就是你的手指離開(kāi)屏幕。
- action_move 手指在屏幕滑動(dòng)。
- action_cancel 當(dāng)一個(gè)view在消費(fèi)某個(gè)事件的時(shí)候,如果你要把這個(gè)事件轉(zhuǎn)給其他的view會(huì)用到這個(gè)。
以上時(shí)最基本的,也就是說(shuō),一個(gè)完整的手勢(shì)定義就是開(kāi)始于down,然后接受其他的事件,然后終止于up。周而復(fù)始。你自己新定義的手勢(shì)必須遵循這個(gè)原則。
Android的事件分發(fā)機(jī)制
首先,清楚一點(diǎn),你的activity優(yōu)先于所有你的activity里面的view獲得觸摸事件。因?yàn)樗悄愕目梢晥D的頂層。然后這個(gè)事件流會(huì)被activity的dispatchTouchEvent() 方法分發(fā)給自己的子view。這是系統(tǒng)框架自己做的事情。我們知道就好。如果子view里面又有viewgroup,那么這個(gè)viewgroup又會(huì)把事件流拿到分發(fā)給自己的子view。這就是一個(gè)視圖結(jié)構(gòu)樹(shù)。

事件流會(huì)呈現(xiàn)一種從上到下,再?gòu)南碌缴系膫鬟f形式。我們講到事件流會(huì)從activity由dispatchtouchevent向下傳遞。如果在傳遞在最底部的view的過(guò)程中還沒(méi)有一個(gè)view宣布對(duì)這個(gè)事件感興趣,那么事件將會(huì)從下到上又把這個(gè)事件傳遞給activity。但是如果這個(gè)過(guò)程中,某個(gè)view比如button宣布對(duì)這個(gè)action_down這個(gè)事件感興趣了,那么這個(gè)事件流將不會(huì)再往下傳遞。這個(gè)時(shí)候如果你的手指拿開(kāi),就產(chǎn)生了一個(gè)action_up的事件。這個(gè)流就會(huì)直接傳到當(dāng)初你對(duì)action_down感興趣的view去。這樣極大的提高了安卓的效率。
Android的事件消費(fèi)
這里說(shuō)的事件消費(fèi)也就是上面提到的view對(duì)某個(gè)事件是否感興趣。實(shí)現(xiàn)機(jī)制就是在view里面調(diào)用onTouchEvent()方法,這個(gè)方法我相信很多人都遇到過(guò)的,它其實(shí)是對(duì)整個(gè)手勢(shì)的事件流的監(jiān)聽(tīng)。如果你想讓一個(gè)view對(duì)某個(gè)事件感興趣,那么判斷如果遇到了這個(gè)事件,return true。表示你對(duì)這個(gè)事件感興趣,那么事件的傳遞將會(huì)停止。并且需要記住的一點(diǎn)是activity的onTouchEvent() 方法是最后調(diào)用的。并不是第一個(gè)。
ViewGroup 對(duì)于事件分發(fā)給子view要復(fù)雜一點(diǎn)。如果你寫(xiě)過(guò)自定義view group,那你知道view group會(huì)對(duì)子view進(jìn)行位置安排(on layout())。而當(dāng)事件傳到某個(gè)view group的時(shí)候,他會(huì)根據(jù)子view的位置來(lái)確定哪些子view對(duì)這個(gè)事件感興趣。如果多個(gè)子view重疊,那viewgroup會(huì)根據(jù)子view加入view group的逆序來(lái)分發(fā)事件。
Android事件的竊取與反竊取
這里說(shuō)的竊取是定義在viewgroup和他的子view之間的。舉個(gè)例子

這里有一個(gè)srcollview作為view group,里面有個(gè)button作為子view。
- 用手指去按下button。這里button宣布對(duì)這個(gè)事件感興趣了。嗯,好的,button兒子準(zhǔn)備接受up事件,然后順利產(chǎn)生一次點(diǎn)擊事件了
- 滑動(dòng)手指。臥槽,什么,怎么又滑動(dòng)手指了,產(chǎn)生了一個(gè)move事件。我們都知道,這樣做了之后,button將不會(huì)在被點(diǎn)擊,也就是說(shuō)他接收不到up的事件了,為什么呢,因?yàn)樗陌职謘crollview把這個(gè)事件給竊取了??觾鹤庸?/li>
- 兒子不聽(tīng)話,非要完成這次點(diǎn)擊。于是兒子來(lái)了一次反竊取,阻止爸爸偷本屬于自己的up事件。于是采取了反竊取手段達(dá)成目的。
很容易理解了吧。竊取就是在viewgroup里面調(diào)用onInteceptTouchEvent()。還記得前面講的那個(gè)action_cancel吧,如果move事件被竊取,那么button酒會(huì)收到一個(gè)cancel的事件,告訴button你不會(huì)再收到接下來(lái)的其他事件了。反竊取就是在button里面調(diào)用requestDisallowTouchIntercept(Boolean boolean)如果傳true,那么就阻止了view group去竊取這個(gè)手勢(shì)。
來(lái)看一下源碼吧。
onInterceptTouchEvent(Motion m)
ViewGroup中
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev);
}
源碼中:
* @param ev The motion event being dispatched down the hierarchy.
* @return Return true to steal motion events from the children and have
* them dispatched to this ViewGroup through onTouchEvent().
* The current target will receive an ACTION_CANCEL event, and no further
* messages will be delivered here.
*/
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
很清楚了吧。大家可以再看一遍源碼的注視!一目了然。默認(rèn)是false,如果重寫(xiě)return true就可以竊取了。
requestDisallowIntercept(Boolean is)
/**
* Called when a child does not want this parent and its ancestors to
* intercept touch events with
* {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.
*
* <p>This parent should pass this call onto its parents. This parent must obey
* this request for the duration of the touch (that is, only clear the flag
* after this parent has received an up or a cancel.</p>
*
* @param disallowIntercept True if the child does not want the parent to
* intercept touch events.
*/
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept);
在源碼中可以看到,一旦對(duì)一個(gè)事件表示了不允許parent竊取(注意我說(shuō)的是一個(gè)事件,而不是事件流),那么會(huì)設(shè)置一個(gè)flag,標(biāo)志知識(shí)這個(gè)事件不允許parent竊取。但是這個(gè)flag會(huì)在parent收到這個(gè)事件的up活著cancel的時(shí)候,就會(huì)重置flag。onInterceptTouchEvent()是一樣的道理,不同的是這個(gè)方法是需要傳入一個(gè)具體的MotionEvent的。還有一點(diǎn)值得提的是,如果你的viewgroup里面的所有子view都對(duì)down這個(gè)事件不感興趣,但是某個(gè)view卻對(duì)up感興趣。那么你一定要在viewgroup的onTouchEvent()方法里面返回true。不然viewgroup的parent不會(huì)再把之后的事件傳給viewgroup了哦!!
總結(jié)
好啦,對(duì)于android觸摸事件的基礎(chǔ)知識(shí)也了解的差不多可以了。未完待續(xù)!~
版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載。