Android開發(fā)中View的事件分發(fā)探秘

View是Android開發(fā)中所有控件的基類,雖然它不屬于四大組件,但是它在android開發(fā)中的地位絲毫不亞于四大組件,甚至于毫不夸張的說,它的重要性和使用場景是超出BroadcastReceiver和ContentProvider。在日常的開發(fā)中跟view打交道的時候太多了,可以說,android開發(fā)中最終的頁面呈現全靠view。而我們在開發(fā)中也碰到使用view給我們帶來的問題和困擾。其中一個很典型和突出的問題(view滑動沖突)就是由于view的事件分發(fā)處理不當導致的,所以深刻理解android開發(fā)中view的分發(fā)機制對于解決這一系列的問題很有必要。

觸摸事件(MotionEvent)的傳遞規(guī)則

所謂的觸摸事件的分發(fā),其實就是對用戶操作view,android系統(tǒng)做出反應——產生MotionEvent事件,系統(tǒng)對這個事件從頂級view一層一層的進行分發(fā),直到一個具體的view接收和消化并最終響應了該事件。(當然這里說的view可能不太準確,因為這個事件可能分發(fā)到最后,整個事件鏈中的所有view都沒有接收或者消化并響應該事件,它最終會被所在的activit所消化。ps:整個原理我們會在后面做具體的解釋說明)

在整個事件鏈分發(fā)過程中,我們主要關注三個方法:

public boolean dispatchTouchEvent(MotionEvent ev);

public boolean onInterceptTouchEvent(MotionEvent ev);

public boolean onTouchEvent(MotionEvent ev);

  • dispatchTouchEvent()方法主要處理整個事件序列的分發(fā),返回值標識當前view是否消耗當前傳遞事件。返回值主要受當前view的touchEvent()和下級view的dispatchTouchEvent()影響(ps:原理稍后進行說明)

  • onInterceptTouchEvent()方法主要用來標識(告知)系統(tǒng),攔截整個事件序列的view是當前view,該方法在整個事件序列中只會有一次為true的返回值,并且如果當前view返回了true,那么在該次事件序列傳遞過程中,其子view的這個方法都不會再被調用。

  • onTouchEvent()方法用來處理觸摸事件,也就是該次事件序列中最終消費事件的方法。當然他它可能會是整個事件序列傳遞中的任意一個view消費該次事件,當然這個事件只能被唯一一個view(或者window,或者activity)所消費

上面說的一大串可能很多人就會懵了,沒關系,我們來看一段從源碼里剝離出來的偽代碼就可以把這三個方法的關系和調用捋清楚:

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

通過以上的講述,我想大家可能會對觸摸事件的傳遞規(guī)則有了一定的認識。接下來我們稍微總結一下。

對于一個View或者一個ViewGroup而言,如果事件傳遞到它,這時它的dispatchTouchEvent()方法會被調用,在dispatchTouchEvent()方法會首先調用自己的onInterceptTouchEvent()判斷是否攔截這次事件

  • 如果onInterceptTouchEvent()返回true則表示攔截該事件,并且它的onTouchEvent()方法會被調用意味著事件自己交給自己處理(ps:如果onTouchEvent()也同樣返回false,那么結果會如何呢?)

  • 反之,如果onInterceptTouchEvent()返回false,那么這個事件就傳遞給它的子View(當然傳遞給子View的前提是它得擁有子View,那么如果當前View沒有子View,那么結果又會怎么樣呢?)

  • 以之上兩條規(guī)則作為傳遞規(guī)則,直至事件被最終處理掉

觸摸事件在Activity的傳遞規(guī)則

當用戶觸摸了頁面時,系統(tǒng)會產生一個MotionEvent事件,該事件系統(tǒng)的一系列處理,在頁面展示層面而言,它會被首先傳遞到用戶操作的Activity(當然也有可能是Service)。之后這個事件的傳遞順序如下:

  • Activity -> Window -> 頂級View(ViewGroup)從頂級View以下就按照前文所說的事件分發(fā)機制去分發(fā)處理。

如果傳遞的過程中所有的View和window都不事件進行響應,那么最終事件會一級一級的往上送,如果所有的View和Window的onTouchEvent()都返回false,那么這個事件會被送回Activity進行處理(Activity的onTouchEvent()方法會被調用)。這個用現實中的例子來描述的話,我給它稱之為“搞不定找老大”。

到這里為止,我們可以得出這樣一個結論:

一次事件必然需要一個處理者,實在沒有處理者和消耗者,那么它會被回傳給大Boss(Activity)。如果一個View的onTouchEvent()方法被執(zhí)行了,那么意味著這個事件序列在正常情況下是不會再往下傳遞的了,如果當前view的onTouchEvent()返回了false,那么這個事件就會被扔給上級進行處理。

這個結論可以解決上文所提的兩個問題。

onTouchEvent()、onTouch()、OnClick()的區(qū)別與聯系

在這邊我就直接給出我測試的結論了,基于這一塊的源碼,感興趣的朋友可以自行查閱、了解。

當一個View被設置了OnTouchListener時,那么如果事件傳遞到了這個View,OnTouchListener的onTouch()方法會被首先調用,如果事件在onTouch()方法中被消耗掉了(返回值為true)。那就沒有onTouchEvent()方法什么事了。只有onTouch()方法返回false時,onTouchEvent()方法才會被執(zhí)行,在onTouchEvent()方法中,如果
設置了OnClickListener,這個時候會去回調OnClick()方法。也就是說,我們平時常用的OnClickListener優(yōu)先級最低,
處于事件傳遞的末端。

我事件我做主

在前文我們所講述事件傳遞規(guī)則有一條主線是不變的,事件的傳遞過程是由外向內的,也就是從父元素往子元素一級級傳遞。這樣的話子元素如果跟父元素都能處理事件時,按照正常事件分發(fā)流程,子元素是別想了。但是這樣不行啊,子元素處理的必要性如果很強,那么這個事件我們就應該從父元素搶占過來。那么通過requestDisallowInterceptTouchEvent() 方法可以實現對父元素的事件分發(fā)過程進行干預,從而實現子元素必要時可以搶占事件的處理和消耗權。

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容