View是Android開發(fā)中所有控件的基類翔冀,雖然它不屬于四大組件导街,但是它在android開發(fā)中的地位絲毫不亞于四大組件,甚至于毫不夸張的說纤子,它的重要性和使用場(chǎng)景是超出BroadcastReceiver和ContentProvider搬瑰。在日常的開發(fā)中跟view打交道的時(shí)候太多了,可以說控硼,android開發(fā)中最終的頁面呈現(xiàn)全靠view泽论。而我們?cè)陂_發(fā)中也碰到使用view給我們帶來的問題和困擾。其中一個(gè)很典型和突出的問題(view滑動(dòng)沖突)就是由于view的事件分發(fā)處理不當(dāng)導(dǎo)致的卡乾,所以深刻理解android開發(fā)中view的分發(fā)機(jī)制對(duì)于解決這一系列的問題很有必要翼悴。
觸摸事件(MotionEvent)的傳遞規(guī)則
所謂的觸摸事件的分發(fā),其實(shí)就是對(duì)用戶操作view幔妨,android系統(tǒng)做出反應(yīng)——產(chǎn)生MotionEvent事件鹦赎,系統(tǒng)對(duì)這個(gè)事件從頂級(jí)view一層一層的進(jìn)行分發(fā),直到一個(gè)具體的view接收和消化并最終響應(yīng)了該事件误堡。(當(dāng)然這里說的view可能不太準(zhǔn)確古话,因?yàn)檫@個(gè)事件可能分發(fā)到最后,整個(gè)事件鏈中的所有view都沒有接收或者消化并響應(yīng)該事件锁施,它最終會(huì)被所在的activit所消化陪踩。ps:整個(gè)原理我們會(huì)在后面做具體的解釋說明)
在整個(gè)事件鏈分發(fā)過程中,我們主要關(guān)注三個(gè)方法:
public boolean dispatchTouchEvent(MotionEvent ev);
public boolean onInterceptTouchEvent(MotionEvent ev);
public boolean onTouchEvent(MotionEvent ev);
dispatchTouchEvent()方法主要處理整個(gè)事件序列的分發(fā),返回值標(biāo)識(shí)當(dāng)前view是否消耗當(dāng)前傳遞事件沾谜。返回值主要受當(dāng)前view的touchEvent()和下級(jí)view的dispatchTouchEvent()影響(ps:原理稍后進(jìn)行說明)
onInterceptTouchEvent()方法主要用來標(biāo)識(shí)(告知)系統(tǒng)膊毁,攔截整個(gè)事件序列的view是當(dāng)前view,該方法在整個(gè)事件序列中只會(huì)有一次為true的返回值基跑,并且如果當(dāng)前view返回了true婚温,那么在該次事件序列傳遞過程中,其子view的這個(gè)方法都不會(huì)再被調(diào)用媳否。
onTouchEvent()方法用來處理觸摸事件栅螟,也就是該次事件序列中最終消費(fèi)事件的方法荆秦。當(dāng)然他它可能會(huì)是整個(gè)事件序列傳遞中的任意一個(gè)view消費(fèi)該次事件,當(dāng)然這個(gè)事件只能被唯一一個(gè)view(或者window力图,或者activity)所消費(fèi)
上面說的一大串可能很多人就會(huì)懵了步绸,沒關(guān)系,我們來看一段從源碼里剝離出來的偽代碼就可以把這三個(gè)方法的關(guān)系和調(diào)用捋清楚:
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;
if (onInterceptTouchEvent(ev)) {
consume = onTouchEvent();
}else{
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
通過以上的講述吃媒,我想大家可能會(huì)對(duì)觸摸事件的傳遞規(guī)則有了一定的認(rèn)識(shí)瓤介。接下來我們稍微總結(jié)一下。
對(duì)于一個(gè)View或者一個(gè)ViewGroup而言赘那,如果事件傳遞到它刑桑,這時(shí)它的dispatchTouchEvent()方法會(huì)被調(diào)用,在dispatchTouchEvent()方法會(huì)首先調(diào)用自己的onInterceptTouchEvent()判斷是否攔截這次事件
如果onInterceptTouchEvent()返回true則表示攔截該事件募舟,并且它的onTouchEvent()方法會(huì)被調(diào)用意味著事件自己交給自己處理(ps:如果onTouchEvent()也同樣返回false祠斧,那么結(jié)果會(huì)如何呢?)
反之拱礁,如果onInterceptTouchEvent()返回false琢锋,那么這個(gè)事件就傳遞給它的子View(當(dāng)然傳遞給子View的前提是它得擁有子View,那么如果當(dāng)前View沒有子View呢灶,那么結(jié)果又會(huì)怎么樣呢吴超?)
以之上兩條規(guī)則作為傳遞規(guī)則,直至事件被最終處理掉
觸摸事件在Activity的傳遞規(guī)則
當(dāng)用戶觸摸了頁面時(shí)填抬,系統(tǒng)會(huì)產(chǎn)生一個(gè)MotionEvent事件烛芬,該事件系統(tǒng)的一系列處理,在頁面展示層面而言飒责,它會(huì)被首先傳遞到用戶操作的Activity(當(dāng)然也有可能是Service)赘娄。之后這個(gè)事件的傳遞順序如下:
- Activity -> Window -> 頂級(jí)View(ViewGroup)從頂級(jí)View以下就按照前文所說的事件分發(fā)機(jī)制去分發(fā)處理。
如果傳遞的過程中所有的View和window都不事件進(jìn)行響應(yīng)宏蛉,那么最終事件會(huì)一級(jí)一級(jí)的往上送遣臼,如果所有的View和Window的onTouchEvent()都返回false,那么這個(gè)事件會(huì)被送回Activity進(jìn)行處理(Activity的onTouchEvent()方法會(huì)被調(diào)用)拾并。這個(gè)用現(xiàn)實(shí)中的例子來描述的話揍堰,我給它稱之為“搞不定找老大”。
到這里為止嗅义,我們可以得出這樣一個(gè)結(jié)論:
一次事件必然需要一個(gè)處理者屏歹,實(shí)在沒有處理者和消耗者,那么它會(huì)被回傳給大Boss(Activity)之碗。如果一個(gè)View的onTouchEvent()方法被執(zhí)行了蝙眶,那么意味著這個(gè)事件序列在正常情況下是不會(huì)再往下傳遞的了,如果當(dāng)前view的onTouchEvent()返回了false褪那,那么這個(gè)事件就會(huì)被扔給上級(jí)進(jìn)行處理幽纷。
這個(gè)結(jié)論可以解決上文所提的兩個(gè)問題式塌。
onTouchEvent()、onTouch()友浸、OnClick()的區(qū)別與聯(lián)系
在這邊我就直接給出我測(cè)試的結(jié)論了峰尝,基于這一塊的源碼,感興趣的朋友可以自行查閱收恢、了解武学。
當(dāng)一個(gè)View被設(shè)置了OnTouchListener時(shí),那么如果事件傳遞到了這個(gè)View派诬,OnTouchListener的onTouch()方法會(huì)被首先調(diào)用劳淆,如果事件在onTouch()方法中被消耗掉了(返回值為true)。那就沒有onTouchEvent()方法什么事了默赂。只有onTouch()方法返回false時(shí),onTouchEvent()方法才會(huì)被執(zhí)行括勺,在onTouchEvent()方法中缆八,如果
設(shè)置了OnClickListener,這個(gè)時(shí)候會(huì)去回調(diào)OnClick()方法疾捍。也就是說奈辰,我們平時(shí)常用的OnClickListener優(yōu)先級(jí)最低,
處于事件傳遞的末端乱豆。
我事件我做主
在前文我們所講述事件傳遞規(guī)則有一條主線是不變的奖恰,事件的傳遞過程是由外向內(nèi)的,也就是從父元素往子元素一級(jí)級(jí)傳遞宛裕。這樣的話子元素如果跟父元素都能處理事件時(shí)瑟啃,按照正常事件分發(fā)流程,子元素是別想了揩尸。但是這樣不行啊蛹屿,子元素處理的必要性如果很強(qiáng),那么這個(gè)事件我們就應(yīng)該從父元素?fù)屨歼^來岩榆。那么通過requestDisallowInterceptTouchEvent() 方法可以實(shí)現(xiàn)對(duì)父元素的事件分發(fā)過程進(jìn)行干預(yù)错负,從而實(shí)現(xiàn)子元素必要時(shí)可以搶占事件的處理和消耗權(quán)。