Android事件分發(fā)機(jī)制

了解Activity的構(gòu)成

一個(gè)Activity包含了一個(gè)Window對象,這個(gè)對象是由PhoneWindow來實(shí)現(xiàn)的。PhoneWindow將DecorView作為整個(gè)應(yīng)用窗口的根View,而這個(gè)DecorView又將屏幕劃分為兩個(gè)區(qū)域:一個(gè)是TitleView,另一個(gè)是ContentView,而我們平時(shí)所寫的就是展示在ContentView中的。

觸摸事件的類型

觸摸事件對應(yīng)的是MotionEvent類,事件的類型主要有如下三種:

ACTION_DOWN
ACTION_MOVE(移動的距離超過一定的閾值會被判定為ACTION_MOVE操作)
ACTION_UP

View事件分發(fā)本質(zhì)就是對MotionEvent事件分發(fā)的過程。即當(dāng)一個(gè)MotionEvent發(fā)生后,系統(tǒng)將這個(gè)點(diǎn)擊事件傳遞到一個(gè)具體的View上。

事件分發(fā)流程

事件分發(fā)過程由三個(gè)方法共同完成:
dispatchTouchEvent:方法返回值為true表示事件被當(dāng)前視圖消費(fèi)掉;返回為super.dispatchTouchEvent表示繼續(xù)分發(fā)該事件,返回為false表示交給父類的onTouchEvent處理。
onInterceptTouchEvent:方法返回值為true表示攔截這個(gè)事件并交由自身的onTouchEvent方法進(jìn)行消費(fèi);返回false表示不攔截,需要繼續(xù)傳遞給子視圖。如果return super.onInterceptTouchEvent(ev), 事件攔截分兩種情況:

1.如果該View存在子View且點(diǎn)擊到了該子View, 則不攔截, 繼續(xù)分發(fā)
給子View 處理, 此時(shí)相當(dāng)于return false。
2.如果該View沒有子View或者有子View但是沒有點(diǎn)擊中子View(此時(shí)ViewGroup
相當(dāng)于普通View), 則交由該View的onTouchEvent響應(yīng),此時(shí)相當(dāng)于return true。

注意:一般的LinearLayout、 RelativeLayout、FrameLayout等ViewGroup默認(rèn)不攔截, 而
ScrollView、ListView等ViewGroup則可能攔截,得看具體情況。
onTouchEvent:方法返回值為true表示當(dāng)前視圖可以處理對應(yīng)的事件;返回值為false表示當(dāng)前視圖不處理這個(gè)事件,它會被傳遞給父視圖的onTouchEvent方法進(jìn)行處理。如果return super.onTouchEvent(ev),事件處理分為兩種情況:

1.如果該View是clickable或者longclickable的,則會返回true, 表示消費(fèi)
了該事件, 與返回true一樣;
2.如果該View不是clickable或者longclickable的,則會返回false, 表示不
消費(fèi)該事件,將會向上傳遞,與返回false一樣。

注意:在Android系統(tǒng)中,擁有事件傳遞處理能力的類有以下三種:

Activity:擁有分發(fā)和消費(fèi)兩個(gè)方法。
ViewGroup:擁有分發(fā)、攔截和消費(fèi)三個(gè)方法。
View:擁有分發(fā)、消費(fèi)兩個(gè)方法。

三個(gè)方法的關(guān)系用偽代碼表示如下:

public boolean dispatchTouchEvent(MotionEvent ev){
  boolean consume = false;
  if(onInterceptTouchEvent(ev)){
    consume = onTouchEvent(ev);
  }
  else{
    consume = child.dispatchTouchEvent(ev);
  }
  return consume;
}

通過上面的偽代碼,我們可以大致了解點(diǎn)擊事件的傳遞規(guī)則:對應(yīng)一個(gè)根ViewGroup來說,點(diǎn)擊事件產(chǎn)生后,首先會傳遞給它,這是它的dispatchTouchEvent就會被調(diào)用,如果這個(gè)ViewGroup的onInterceptTouchEvent方法返回true就表示它要攔截當(dāng)前事件,接著事件就會交給這個(gè)ViewGroup處理,這時(shí)如果它的mOnTouchListener被設(shè)置,則onTouch會被調(diào)用,否則onTouchEvent會被調(diào)用。在onTouchEvent中,如果設(shè)置了mOnCLickListener,則onClick會被調(diào)用。只要View的CLICKABLE和LONG_CLICKABLE有一個(gè)為true,onTouchEvent()就會返回true消耗這個(gè)事件。如果這個(gè)ViewGroup的onInterceptTouchEvent方法返回false就表示它不攔截當(dāng)前事件,這時(shí)當(dāng)前事件就會繼續(xù)傳遞給它的子元素,接著子元素的dispatchTouchEvent方法就會被調(diào)用,如此反復(fù)直到事件被最終處理。

一些重要的結(jié)論:

1、事件傳遞優(yōu)先級:onTouchListener.onTouch > onTouchEvent > onClickListener.onClick。
2、正常情況下,一個(gè)時(shí)間序列只能被一個(gè)View攔截且消耗。因?yàn)橐坏┮粋€(gè)元素?cái)r截了此事件,那么同一個(gè)事件序列內(nèi)的所有事件都會直接交給它處理(即不會再調(diào)用這個(gè)View的攔截方法去詢問它是否要攔截了,而是把剩余的ACTION_MOVE、ACTION_DOWN等事件直接交給它來處理)。特例:通過將重寫View的onTouchEvent返回false可強(qiáng)行將事件轉(zhuǎn)交給其他View處理。
3、如果View不消耗除ACTION_DOWN以外的其他事件,那么這個(gè)點(diǎn)擊事件會消失,此時(shí)父元素的onTouchEvent并不會被調(diào)用,并且當(dāng)前View可以持續(xù)收到后續(xù)的事件,最終這些消失的點(diǎn)擊事件會傳遞給Activity處理。
4、ViewGroup默認(rèn)不攔截任何事件(返回false)。
5、View的onTouchEvent默認(rèn)都會消耗事件(返回true),除非它是不可點(diǎn)擊的(clickable和longClickable同時(shí)為false)。View的longClickable屬性默認(rèn)都為false,clickable屬性要分情況,比如Button的clickable屬性默認(rèn)為true,而TextView的clickable默認(rèn)為false。
6、View的enable屬性不影響onTouchEvent的默認(rèn)返回值。
7、通過requestDisallowInterceptTouchEvent方法可以在子元素中干預(yù)父元素的事件分發(fā)過程,但是ACTION_DOWN事件除外。

如何解決View的事件沖突?舉個(gè)開發(fā)中遇到的例子?

常見開發(fā)中事件沖突的有ScrollView與RecyclerView的滑動沖突、RecyclerView內(nèi)嵌同時(shí)滑動同一方向。
滑動沖突的處理規(guī)則:

對于由于外部滑動和內(nèi)部滑動方向不一致導(dǎo)致的滑動沖突,可以根據(jù)滑動的方向判斷誰來攔截事件。
對于由于外部滑動方向和內(nèi)部滑動方向一致導(dǎo)致的滑動沖突,可以根據(jù)業(yè)務(wù)需求,規(guī)定何時(shí)讓外部View攔截事件,何時(shí)由內(nèi)部View攔截事件。
對于上面兩種情況的嵌套,相對復(fù)雜,可同樣根據(jù)需求在業(yè)務(wù)上找到突破點(diǎn)。

滑動沖突的實(shí)現(xiàn)方法:

外部攔截法:指點(diǎn)擊事件都先經(jīng)過父容器的攔截處理,如果父容器需要此事件就攔截,否則就不攔截。具體方法:需要重寫父容器的onInterceptTouchEvent方法,在內(nèi)部做出相應(yīng)的攔截。
內(nèi)部攔截法:指父容器不攔截任何事件,而將所有的事件都傳遞給子容器,如果子容器需要此事件就直接消耗,否則就交由父容器進(jìn)行處理。具體方法:需要配合requestDisallowInterceptTouchEvent方法。

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

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

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