Android事件分發機制

參考:
更簡單的學習Android事件分發

1. 相關方法

相關方法 方法功能 Activity ViewGroup View
dispatchTouchEvent() 事件分發 Yes Yes Yes
onInterceptTouchEvent() 事件攔截 No Yes No
onTouchEvent() 事件消費 Yes Yes Yes

事件分發: dispatchTouchEvent

  • 返回true,則表示在當前View或其子View(子子...View)中,找到了處理事件的View;
  • 返回false,則表示在當前View或其子View(子子...View)中沒有處理事件的View;

事件攔截: onInterceptTouchEvent

  • 返回true,則表示當前View攔截這個事件并交予當前View的onTouchEvent()處理,不會再向子View傳遞這個事件;
  • 返回false,則表示當前View不攔截這個事件,只有他的子View的dispatchTouchEvent()返回false,才會分發到當前View的onTouchEvent()方法,否則當前View不處理這個事件

事件消費: onTouchEvent

  • 返回true,則表示當前View消費掉這個事件;
  • 返回false,則表示當前View不消費這個時間;

2. 具體分析

首先,我們來說一下觸摸事件(不考慮多點觸摸),一組觸摸時間以一個DOWN事件開始,后跟0個或多個MOVE事件,最后跟一個UP或CANCEL事件。事件分發是從外往內分發的或者說從上到下,即事件從父層View向子層View分發。


布局

接下來我們根據上圖來分析以下五種基本情況,復雜的事件分發一般都是這幾種情況的組合,掌握了基本事件的分發機制,復雜的也就迎刃而解了:

  • View消費DOWN、MOVE、UP事件
  • View不消費DOWN事件
  • View不消費MOVE、UP事件
  • ViewGroup攔截DOWN事件
  • ViewGroup攔截MOVE、UP事件

一般情況下DOWN和MOVE、UP的分發機制不同,所以我們要分開討論(當View消費觸摸事件時,事件的分發一致,所以我們可合并分析)

1). 事件分發(View消費)

事件傳遞過程:

DOWN事件:

從Activity#dispatchTouchEvent開始分發,傳遞到ViewGroup#dispatchTouchEvent,再傳遞到ViewGroup#onInterceptTouchEvent,ViewGroup#onInterceptTouchEvent返回false表示不攔截,所以事件再往下分發到View#dispatchTouchEvent,繼而傳遞到View#onTouchEvent,View#onTouchEvent返回true表示消費掉這個事件,返回的結果true依次通過View#dispatchTouchEvent、ViewGroup#dispatchTouchEvent再返回給Activity#dispatchTouchEvent表示這個事件被消費掉了,后續的MOVE、UP事件都將傳遞給View#onTouchEvent處理。

MOVE、UP事件:

與DOWN事件相同。

通過上面的傳遞過程,我們可以得出:

  • onInterceptTouchEvent返回false表示不攔截事件,事件將繼續向下分發,傳遞到它子View的dispatchTouchEvent中
  • onTouchEvent返回true表示消費這個事件,返回結果true將通過dispatchTouchEvent以及父View中的dispatchTouchEvent傳遞回Activity,那么這個事件序列中的后續事件都將交給它處理,

注:
View如果需要處理觸摸事件必須(clickable == true || longClickable == true),否則將不會分發事件給View

2). 事件分發(View不消費)

事件傳遞過程:

DOWN事件:

從Activity#dispatchTouchEvent開始分發,傳遞到ViewGroup#dispatchTouchEvent,再傳遞到ViewGroup#onInterceptTouchEvent,ViewGroup#onInterceptTouchEvent返回false表示不攔截,所以事件再往下分發到View#dispatchTouchEvent,繼而傳遞到View#onTouchEvent,View#onTouchEvent返回false表示不消費這個事件,返回的結果false再傳遞給View#dispatchTouchEvent,ViewGroup#dispatchTouchEvent得知View不處理這個事件所以再把事件交給ViewGroup#onTouchEvent,ViewGroup#onTouchEvent返回false表示自己也不處理這個事件,并把結果false反回給ViewGroup#dispatchTouchEvent,Activity#dispatchTouchEvent得知ViewGroup不處理這個事件所以再把這個事件交給Activity#onTouchEvent處理,Activity#onTouchEvent也返回false表示自己也不處理這個事件,最后這個結果false傳回了Activity#dispatchTouchEvent中,表示找了一圈這個事件沒人消費。

MOVE、UP事件:

因為DOWN事件沒人處理,所以這個事件序列中的后續事件MOVE、UP就不再分發了,而是直接交給Activity#onTouchEvent處理,Activity#onTouchEvent返回false表示自己不處理。

通過上面的傳遞過程,我們可以得出:

  • 如果onTouchEvent返回false表示不處理這個事件,這個事件則會向上傳給父View的onTouchEvent中,如果都不處理則會傳遞回Activity#onTouchEvent中
  • 如果View或ViewGroup不消費DOWN事件,那么這個序列的后續事件MOVE、UP將不會再傳遞給它處理
3). 事件攔截

事件傳遞過程:

DOWN事件:

從Activity#dispatchTouchEvent開始分發,傳遞到ViewGroup#dispatchTouchEvent,再傳遞到ViewGroup#onInterceptTouchEvent,ViewGroup#onInterceptTouchEvent返回true表示攔截,所以事件傳遞給了ViewGroup#onTouchEvent,并不會再傳給View,ViewGroup#onTouchEvent返回true表示處理這個事件,并返回給ViewGroup#dispatchTouchEvent,ViewGroup#dispatchTouchEvent把結果true返回給Activity#dispatchTouchEvent表示找到處理這個事件的View了。

MOVE、UP事件:

這個事件序列中的后續事件MOVE、UP分發到ViewGroup#dispatchTouchEvent是會判斷(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null),因為既不是DOWN事件,子View中有沒有處理到DOWN事件所以mFirstTouchTarget == null就不再傳遞給ViewGroup#onInterceptTouchEvent,而是直接傳給ViewGroup#onTouchEvent,ViewGroup#onTouchEvent返回true表示處理這個事件,并返回給ViewGroup#dispatchTouchEvent,ViewGroup#dispatchTouchEvent把結果true返回給Activity#dispatchTouchEvent表示找到處理這個事件的View了。

通過上面的傳遞過程,我們可以得出:

  • onInterceptTouchEvent返回為true表示攔截事件,事件將直接傳遞給它自己的onTouchEvent處理,不會再傳遞給子View
  • DOWN事件被當前ViewGroup的onInterceptTouchEvent攔截,這個序列的后續事件MOVE、UP將不會傳遞給它的onInterceptTouchEvent,而直接傳遞給它的onTouchEvent方法處理

注:
1.如果DOWN事件的傳遞給onInterceptTouchEvent時沒有攔截,MOVE事件傳遞給onInterceptTouchEvent時攔截,這樣將傳給View一個CANCEL事件,之后的MOVE事件就不再傳遞給View,而直接傳遞給ViewGroup#onTouchEvent處理。
2.子View中可以調用getParent().requestDisallowInterceptTouchEvent(true);來阻止父View攔截觸摸事件,父View就不會再調用onInterceptTouchEvent攔截事件了。

3. 總結

  • 如果某個View消費了DOWN事件,那么通常情況下后續的MOVE、UP事件也將交給它處理,但是可以調用父View的onInterceptTouchEvent進行攔截
  • 如果某個View不消費DOWN事件,那么這個系列后續事件也不會交給它處理
  • 一個事件如果都沒有View消費,那么將由Activity處理
  • 如果事件處理的結果為true,那么結果會不斷返回給父View#dispatchTouchEvent方法,知道傳遞回Activity
  • 如果事件傳遞的結果為false,回傳的結果不斷調用父View#onTouchEvent方法,直到找到消費事件的View或者傳遞回Activity
  • View如果需要處理觸摸事件必須(clickable == true || longClickable == true)
    ,否則將不會分發事件給View
  • 如果DOWN事件的傳遞給onInterceptTouchEvent時沒有攔截,MOVE事件傳遞給onInterceptTouchEvent時攔截,這樣將傳給View一個CANCEL事件,之后的MOVE事件就不再傳遞給View,而直接傳遞給ViewGroup#onTouchEvent處理。
  • 子View中可以調用getParent().requestDisallowInterceptTouchEvent(true);來阻止父View攔截觸摸事件,父View就不會再調用onInterceptTouchEvent攔截事件了。
  • onInterceptTouchEvent返回為true表示攔截事件,事件將直接傳遞給它自己的onTouchEvent處理,不會再傳遞給子View
  • DOWN事件被當前ViewGroup的onInterceptTouchEvent攔截,這個序列的后續事件MOVE、UP將不會傳遞給它的onInterceptTouchEvent,而直接傳遞給它的onTouchEvent方法處理

本人技術有限,歡迎指正,謝謝!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容