View的事件傳遞機(jī)制

先說兩個(gè)重要的概念:

1.同一事件序列事件序列:即手指觸摸屏幕產(chǎn)生ACTION_DOWN開始,其中包含不定數(shù)量的ACTION_MOVE等,以ACTION_UP結(jié)束,即手指離開屏幕那一刻。這就是一個(gè)事件序列。

2.事件的傳遞順序:當(dāng)一個(gè)點(diǎn)擊操作產(chǎn)生之后,事件最先傳遞給當(dāng)前的Activity,由Activity的dispatchTouchEvent來進(jìn)行事件的分發(fā),具體的工作是由Activity內(nèi)部的Window(PhoneWindow)來完成的。Window會(huì)將時(shí)間傳遞給Activity的頂級(jí)View DecorView,即setContentView所設(shè)置的View的父容器,通過Activity.getWindow.getDecorView()可以獲得。Activity的dispatchTouchEvent完整源碼如下:

public boolean dispatchTouchEvent(MotionEvent ev) {

if (ev.getAction() == MotionEvent.ACTION_DOWN) {

onUserInteraction();

}

if (getWindow().superDispatchTouchEvent(ev)) {

return true;

}

return onTouchEvent(ev);

}

這段代碼比較簡(jiǎn)單,也很好理解。其中onUserInteraction是一個(gè)空實(shí)現(xiàn)。按照上面的邏輯,其實(shí)在一個(gè)事件到來時(shí),通過重寫Activity的dispatchTouchEvent方法可以做很多事情。當(dāng)所有的View的onTouchEvent都返回了false,那么Activity將調(diào)用自己的onTouchEvent來處理事件。

View的點(diǎn)擊事件分發(fā)過程由三個(gè)很重要的方法來完成:dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent。

public boolean dispatchTouchEvent(MotionEvent e):用來進(jìn)行事件的分發(fā),如果當(dāng)前事件能夠傳遞給當(dāng)前View,那么此方法一定會(huì)被調(diào)用,返回結(jié)果受當(dāng)前View的onTouchEvent和下級(jí)View的dispatchTouchEvent方法的影響,表示是否消耗當(dāng)前事件。

public boolean onInterceptTouchEvent(MotionEvent e):在上面方法的內(nèi)部調(diào)用,注意,此方法只有ViewGroup才有,用來判斷ViewGroup是否攔截某個(gè)事件,如果攔截某個(gè)事件,那么在同一個(gè)事件序列當(dāng)中,此方法不會(huì)被再次調(diào)用,返回結(jié)果表示是否攔截當(dāng)前事件。

public boolean onTouchEvent(MotionEvent e):在dispatchTouchEvent方法中調(diào)用,用來處理點(diǎn)擊事件,返回結(jié)果表示是否消耗當(dāng)前事件。

上述三個(gè)方法的關(guān)系可以大概用下面的偽代碼表示:

public boolean dispatchTouchEvent(MotionEvent ev){

boolean consume = false;

if(onInterceptTouchEvent(ev)){

consume = onTouchEvent(ev);

}else{

consume = child.dispatchTouchEvent(ev);

if(consume==false && ev.getAction == MotionEvent.ACTION_DOWN){

onTouchEvent(ev);

}

}

return consume;

}

通過上訴代碼可以大致看到點(diǎn)擊事件的傳遞規(guī)則:對(duì)于一個(gè)根ViewGroup來說,點(diǎn)擊事件產(chǎn)生以后,首先會(huì)傳遞給它,這時(shí)它的dispatchTouchEvent方法會(huì)被調(diào)用,如果這個(gè)ViewGroup的onInterceptTouchEvent方法返回true,就代表它要攔截事件,接著事件就會(huì)交給它處理,即調(diào)用ViewGroup的onTouchEvent方法;如果這個(gè)ViewGroup的onInterceptTouchEvent返回了false,就表示它不攔截當(dāng)前事件,這時(shí),這時(shí)當(dāng)前事件就會(huì)繼續(xù)傳遞給它的子元素,接著子元素的dispatchTouchEvent方法就會(huì)被調(diào)用,如此反復(fù)。如果子元素沒有處理事件,且事件是ACTION_DOWN的話,那么ViewGroup會(huì)自己處理,如果此時(shí)它也不處理的這個(gè)ACTION_DOWN的話(返回了false),那么會(huì)繼續(xù)交給它的上級(jí)ViewGroup來處理,如此反復(fù)。最后都沒有處理的話,會(huì)重新傳遞到Activity中。

根據(jù)事件傳遞的機(jī)制,可以得出以下結(jié)論:

1)同一個(gè)事件序列是指從手指接觸屏幕的那一刻起,到手指離開屏幕的那一刻結(jié)束,在這個(gè)過程中所產(chǎn)生的一系列事件,這個(gè)事件序列以MotionEvent.ACTION_DOWN事件開始,中間含有數(shù)量不定的MotionEvent.ACTION_MOVE事件,最終以MotionEvent.ACTION_UP事件結(jié)束。

2)正常情況下,一個(gè)事件序列只能被一個(gè)View攔截且消耗。這一條的原因可以參考(3),因?yàn)橐坏┮粋€(gè)元素?cái)r截了某次事件,那么同一個(gè)事件序列內(nèi)的所有事件都會(huì)直接交給它處理,因此同一個(gè)事件序列中的事件不能分別由兩個(gè)View同時(shí)處理,但是通過特殊的手段可以做到,比如一個(gè)View將本身該自己處理的事件通過onTouchEvent強(qiáng)行傳遞給其他View處理。

3)某個(gè)View一旦決定攔截,那么這一個(gè)事件序列都只能由它來處理(如果事件序列能夠傳遞給它的話),并且它的onInterceptTouchEvent不會(huì)再被調(diào)用。這條也很好理解,就是說當(dāng)一個(gè)View決定攔截一個(gè)事件后,那么系統(tǒng)會(huì)把同一個(gè)事件序列內(nèi)的其他方法都直接交給它來處理,因此 就不用再調(diào)用 這個(gè)View的onInterceptTouchEvent去詢問它是否要攔截了。

4)某個(gè)View一旦開始處理事件,如果它不消耗ACTION_DOWN事件 (onTouchEvent返回了false),那么同一事件序列中的其他事件都不會(huì)再交給它來處理,并且事件將重新交由它的父元素去處理,即父元素的onTouchEvent會(huì)被調(diào)用。意思就是事件(ACTION_DOWN)一旦交給一個(gè)View處理,那么它就必須消耗掉,否則同一事件序列中剩下的事件就不再交給它來處理了。對(duì)于非ACTION_DOWN事件參看第5條。

5)如果View不消耗除ACTION_DOWN以外的其他事件,那么這個(gè)點(diǎn)擊事件會(huì)消失,此時(shí)父元素的onTouchEvent并不會(huì)被調(diào)用,并且當(dāng)前View可以持續(xù)收到后續(xù)的事件,最終這些消失的點(diǎn)擊事件會(huì)傳遞給Activity處理。

6)ViewGroup默認(rèn)不攔截任何事件。Android源碼中VIewGroup的onInterceptTouchEvent方法 默認(rèn)返回false。

7)View沒有onInterceptTouchEvent方法,一旦有點(diǎn)擊事件傳遞給它,那么它的onTouchEvent方法就會(huì)被調(diào)用。

8)View的onTouchEvent默認(rèn)都會(huì)消耗事件(返回true),除非它是不可點(diǎn)擊的(clickable 和 longClickable 同時(shí)為false)。View的longClickable屬性默認(rèn)都為false,clickable屬性要分情況,比如Button的clickable屬性默認(rèn)為true,而TextView的clickable屬性默認(rèn)為false。通過setClickable和setLongClickable可以分別改變View的clickable和longClickable屬性。setOnClickListener會(huì)自動(dòng)將View的clickable屬性設(shè)為true,setOnLongClickListener同理。

9)View的enable屬性不影響onTouchEvent的默認(rèn)返回值。哪怕一個(gè)View是disable狀態(tài)的,只要它的clickable 或者 lonClickable有一個(gè)為true,那么它的onTouchEvent(默認(rèn)不重寫的情況下)就返回true。

10) onClick會(huì)發(fā)生的前提是當(dāng)前View是可點(diǎn)擊的,并且它收到了ACTION_DOWN 和ACTION_UP事件。

11)事件傳遞的過程是由外向內(nèi)的,即事件總是先傳遞給父元素,然后再由父元素分發(fā)給子View,通過調(diào)用父元素的requestDisallowInterceptTouchEvent方法可以在子元素中干預(yù)父元素的的事件頒發(fā)過程,但是ACTION_DOWN事件除外,因?yàn)楫?dāng)ACTION_DOWN這個(gè)事件到來的時(shí)候,會(huì)重置這個(gè)標(biāo)記位。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容