Android的事件分發主要有這幾個角色:Activity、Window、ViewGroup和View。當Activity接收到事件時,會將事件傳遞給Window,而後Window將事件傳遞給頂層容器DecorView(繼承自FrameLayout),事件分發由此開始。java
這邊我將對DOWN、MOVE和UP事件結合源碼單獨分析。面試
首先先明確幾個概念:小程序
當Activity接收到事件時,Activity的dispatchTouchEvent方法會被調用。性能優化
Activity.java架構
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); } 複製代碼
從代碼中能夠看到,Activity收到事件後將事件交由Windows處理ide
PhoneWindow.java源碼分析
@Override public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); } 複製代碼
Window會將事件交由DecorView處理post
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { ... public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); } } 複製代碼
由此能夠看到事件傳遞給了ViewGroup,View事件分發由此開始。性能
首先分析DOWN事件,當咱們觸摸手機屏幕的一瞬間,Activity接收到DOWN事件,事件由Activity傳遞到Window,再到DecorView。當DecorView接收到事件,會調用ViewGroup的dispatchTouchEvent方法。學習
因爲是DOWN事件傳遞到ViewGroup,在dispatchTouchEvent方法中首先會重置觸摸狀態,包括清除保存處理事件View的單鏈表,所以每次DOWN事件表明一個新的事件序列的開始,這點以後會具體分析。
if (actionMasked == MotionEvent.ACTION_DOWN) { //重置全部的觸摸狀態 cancelAndClearTouchTargets(ev); resetTouchState(); } 複製代碼
因爲是DOWN事件,則先會去判斷當前容器是否禁止攔截事件。默認狀況下,父容器能夠攔截事件,此時會調用onInterceptTouchEvent方法,該方法默認返回false;若父容器被禁止攔截事件,則不會調用onInterceptTouchEvent方法。
ViewGroup#dispatchTouchEvent
final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { // 經過標誌位判斷是否禁止攔截事件,默認狀況爲容許攔截事件 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { //容許攔截事件 intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { //禁止該容器攔截事件 intercepted = false; } } else { //攔截事件 intercepted = true; } 複製代碼
這裏先看默認狀況,事件沒有被父容器攔截,即intercepted爲false。此時會去遍歷該ViewGroup的子View,尋找可以處理事件的View,若找到發生觸摸事件的View,將事件分發給對應的子View,若View可以處理事件,也就是子View的dispatchTouchEvent方法返回true,則將該處理事件的View加入mFirstTouchTarget這個鏈表中,並標記當前DOWN事件已被處理。
ViewGroup#dispatchTouchEvent
TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { //未取消未攔截 if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { //遍歷全部的子View,尋找處理事件的View final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); ... if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { //找處處理事件的子View,保存該子View newTouchTarget = addTouchTarget(child, idBitsToAssign); //事件已經分發 alreadyDispatchedToNewTouchTarget = true; break; } } } } 複製代碼
父ViewGroup經過調用dispatchTransformedTouchEvent 將事件分發給對應的子View。子View處理了DOWN事件,也就是子View的dispatchTouchEvent方法返回true,從而會使得dispatchTransformedTouchEvent方法返回true。
ViewGroup.java
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { ... if (child == null) { handled = super.dispatchTouchEvent(transformedEvent); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; transformedEvent.offsetLocation(offsetX, offsetY); if (! child.hasIdentityMatrix()) { transformedEvent.transform(child.getInverseMatrix()); } handled = child.dispatchTouchEvent(transformedEvent); } // Done. transformedEvent.recycle(); return handled; } 複製代碼
若沒有找到可以處理事件的子View,此時事件會交給當前ViewGroup來處理。沒有找處處理DOWN事件的子View,也就是mFirstTouchTarget這個鏈表沒有被賦值,此時爲null。此時經過dispatchTransformedTouchEvent將事件傳遞給當前ViewGroup的父類,調用View的dispatchTouchEvent方法進行事件處理。
ViewGroup#dispatchTouchEvent
if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } 複製代碼
接下來事件傳遞到了View,下方是View的dispatchTouchEvent方法。
View.java
public boolean dispatchTouchEvent(MotionEvent ev){ ... ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } } 複製代碼
事件到了View的dispatchTouchEvent方法,先會去判斷事件是否由OnTouchListener消費掉且View可用,若事件被消費,則該DOWN事件處理結束。若事件未被OnTouchListener消費掉,則將事件交由View的onTouchEvent方法。
View.java
public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; if ((viewFlags & ENABLED_MASK) == DISABLED) { //當前View不可用 if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } //若View可點擊或者可長按,則事件被消費 return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); } ... if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { //若View可點擊或者長按,則事件被消費 switch (event.getAction()) { case MotionEvent.ACTION_DOWN: ... break; } return true; } return false; } } 複製代碼
上述2.1.1的前提是父容器沒有攔截事件,也就是intercepted的值爲false。若此時intercepted值未true(當onInterceptToucnEvent方法返回true)。此時不會去尋找處理事件的子View,也就是mFirstTouchTarget爲null,一樣事件會交由ViewGroup父類的dispatchTouchEvent方法處理。
DOWN事件的分發流程如圖所示,總的來講能夠概括一下幾點:
上述2.2分析了DOWN事件的分發,接下來先分析UP事件的分發。 當手機擡起屏幕的一瞬間,Activity會接收到UP事件,Activity將UP事件傳遞給Window,Window將事件傳遞給DecorView,DecorView父類ViewGroup的dispatchTouchEvent方法被調用。
因爲dispatchTouchEvent方法接收到的是UP事件,若mFirstTouchTarget不爲null,此時表明存在處理DOWN和MOVE事件的子View。mFirstTouchTarget是一個鏈表,用於保存處理事件的子View,在DOWN事件被子View處理後再子View的父容器內被賦值,至於要用一個鏈表的緣由是存在多點觸控的狀況,這裏只考慮單點觸控的事件分發。
存在處理DOWN和MOVE事件的子View,也就是mFirstTouchTarget不爲空。在mFirstTouchTarget不爲空的狀況下,會去判斷當前容器是否禁止攔截事件。默認狀況下爲不攔截事件,此時會調用onInterceptTouchEvent方法,該方法默認返回false;
ViewGroup#dispatchTouchEvent
final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { // 經過標誌位判斷是否禁止攔截事件,默認狀況爲容許攔截事件 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { //容許攔截事件 intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { //禁止該容器攔截事件 intercepted = false; } } else { //攔截事件 intercepted = true; } 複製代碼
2.3.1.1 當前容器不攔截UP事件
當intercepted爲false時,此時爲默認狀況,表明當前容器不攔截UP事件,事件被分發給保存在mFirstTouchTarget的子View。
ViewGroup#dispatchToucnEvent
if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; //遍歷這個單鏈表 while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; //將事件分發給子View if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } 複製代碼
能夠看到事件傳遞到了dispatchTransformedTouchEvent內部,因爲child不爲null,則會調用child的dispatchTouchEvent方法將事件分發給子View。
ViewGroup.java
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { ... if (child == null) { handled = super.dispatchTouchEvent(transformedEvent); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; transformedEvent.offsetLocation(offsetX, offsetY); if (! child.hasIdentityMatrix()) { transformedEvent.transform(child.getInverseMatrix()); } handled = child.dispatchTouchEvent(transformedEvent); } // Done. transformedEvent.recycle(); return handled; } 複製代碼
2.3.1.2 當前容器攔截UP事件
當intercepted的值未true時,表明當前UP事件被當前容器攔截。UP事件被當前容器攔截,可是以前的DOWN和MOVE事件都被子View處理了,此時mFirstTouchTarget不爲空,因此此時走else分支,取消當前的UP事件,變爲CANCEL事件,往下分發或者交由本身處理,而且此時會在遍歷時清空保存在mFirstTouchTarget中處理事件的子View,最終mFirstTouchTarget的值爲空。
ViewGroup#dispatchTouchEvent
if (mFirstTouchTarget == null) { handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { //因爲事件被攔截,intercepted爲true,cancelChild爲true,表明取消事件 final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { //遍歷清空鏈表 if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } 複製代碼
因爲是UP事件,最終會清除View的觸摸狀態
ViewGroup#dispatchTouchEvent
if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { resetTouchState(); } 複製代碼
當mFirstTouchTarget爲空時,不存在處理事件的子View,此時容器父類View的dispatchTouchEvent方法會接收到事件。
if (mFirstTouchTarget == null) { handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { ... } 複製代碼
事件傳遞到View的dispatchTouchEvent方法,一樣先會去判斷事件是否由OnTouchListener消費掉,若事件被消費且View可用,則該UP事件處理結束。若事件未被OnTouchListener消費掉,則將事件交由View的onTouchEvent方法。
View.java
public boolean dispatchTouchEvent(MotionEvent ev){ ... ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } } 複製代碼
事件傳遞到View的onTouchEvent中,能夠看到在UP的時候在條件知足的狀況下會執行單擊事件,同時事件被消費。
View.java
public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; if ((viewFlags & ENABLED_MASK) == DISABLED) { //View不可用,但View可單擊或者長按,一樣能夠消費事件 return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); } if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { //View 可點擊或者長按 switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { .. if (!mHasPerformedLongPress) { removeLongPressCallback(); if (!focusTaken) { if (mPerformClick == null) { mPerformClick = new PerformClick(); } //執行單擊事件 if (!post(mPerformClick)) { performClick(); } } } } break; } return true; } return false; } 複製代碼
UP事件交給ViewGroup本身處理時,除了在UP事件會在條件知足下觸發單擊事件,其他的流程和ViewGroup處理DOWN事件相似。
MOVE事件和UP事件流程類型
想學習更多Android知識,請加入Android技術開發企鵝交流 7520 16839
進羣與大牛們一塊兒討論,還可獲取Android高級架構資料、源碼、筆記、視頻
包括 高級UI、Gradle、RxJava、小程序、Hybrid、移動架構、React Native、性能優化等全面的Android高級實踐技術講解性能優化架構思惟導圖,和BATJ面試題及答案!
羣裏免費分享給有須要的朋友,但願可以幫助一些在這個行業發展迷茫的,或者想系統深刻提高以及困於瓶頸的朋友,在網上博客論壇等地方少花些時間找資料,把有限的時間,真正花在學習上,因此我在這免費分享一些架構資料及給你們。但願在這些資料中都有你須要的內容。