事件分發是android裏的解決事件衝突的一種機制。一般我們的佈局都是一層疊着一層,那麼當我們手指點擊或者滑動的時候,屏幕怎麼知道哪一個控件該響應,哪一個控件該滑動呢?這依靠的就是事件分發機制。
本次文章源碼細節不扣,但是主要流程會列出來,感興趣的可以去源碼中找對應的邏輯,有疑問可以留言哦。
目錄
1.流程解讀:
從Activity--->DecorView:
當我們在activity中觸摸屏幕點擊的時候,它的流程是這樣的:
基本流程:
- 從activity的dispatchTouchEvent開始,調用到DecorView的dispatchTouchEvent()(DevorView是ViewGroup的子類,使用ViewGroup的事件分發機制)
- DecorView是所有view的父佈局,它的dispatchTouchEvent() 會遞歸從外到內進行事件分發。
從DecorView到子view:
DecorView和它的ziview是父佈局和子佈局的關係,所以他們的流程就是父佈局到子佈局事件分發的過程,之後所有的過程都是這樣遞歸的。記住一點,事件分發的流程順序是從父佈局到子佈局。
事件分發涉及到的幾個關鍵方法:
- ①dispatchTouchEvent() // 事件分發的總調度方法
- ②requestDisallowInterceptTouchEvent() // 子view限制父類不能攔截事件
- ③onInterceptTouchEvent() // 決定當前的view是否攔截事件
- ④onTouchEvent() // 當前view事件操作。返回值代表是否消費了事件
其中③④是在①中執行的,②是在外部執行的。
ok,下面從源碼的角度分析,在這之前,先貼幾條結論:
- 一般講事件的開始和結束是指事件流的開始和結束。一個事件流指一系列操作,一般是以DOWN事件開始,UP或cancel事件結束。例如 DOWN-->MOVE-->MOVE-->....-->UP 這是一個事件流。
- 一個事件流中,如果一個View在DOWN事件的時候消費了事件,那麼在父view不攔截的情況下,後續事件則都由它處理。
- 一個事件流中,父view可以在任何階段攔截事件。當父view攔截了事件,那麼後續的事件就由父view來接管了。
- 子view可以通過設置parent.requestDisallowInterceptTouchEvent(true)來禁止父view攔截事件。
下面進入源碼了,主要代碼都在ViewGroup#dispatchTouchEvent()中,代碼就不貼了。可以去看一下這個方法的源碼,大致流程如下:
- ① 處理down事件:如果是DOWN事件,那麼清除觸摸對象mFirstTouchTarget,重置狀態。
- ② 檢查事件攔截:通過狀態和onInterceptTouchEvent() 的返回值來設置intercepted
- ③ 如果不攔截&不取消(其他過濾條件省略)&當前事件是DOWN事件,按照從前往後的順序遍歷子view。遍歷過程:
- 如果找到子view消費了事件,那麼觸摸對象mFirstTouchTarget賦值,alreadyDispatchedToNewTouchTarget=true,跳出循環。
- ④ 如果觸摸對象mFirstTouchTarget爲空,那麼自己消費事件,即執行自己的onTouchEvent()
- ⑤ 如果觸摸對象不爲空,則判斷是否已經處理過(依據alreadyDispatchedToNewTouchTarget和target)
- --如果未處理過:判斷是否攔截(即intercepted)
- --如果不攔截,繼續交給target處理事件
- --如果攔截,則向target傳遞一個cancel事件,然後清除mFristTouchTarget。 然後下一個後續事件中自己就消費事件了。
上面的流程在源碼中都能找到,說的比較詳細,下面我們來從實際場景分析以上流程。
2.場景分析:
舉例三個常見的場景
場景一:
父view不攔截,子view消費事件。
事件流開始===
1.DOWN事件:
經過①②後:
mFirstTouchTarget=null
intercepted=false
經過③後:
mFirstTouchTarget=taget
alreadyDispatchedToNewTouchTarget=true
這時已經處理了事件
④過
⑤過
2.MOVE事件
經過①②,
mFirstTouchTarget=target
intercepted=false
③過
④過
⑤滿足觸摸對象不爲空,未處理過,不攔截,繼續交給target處理事件
之後的MOVE/UP事件流程同2
===事件流結束
場景二:
父view攔截事件,子view消費事件
事件流開始===
1.DOWN事件:
經過①②後:
mFirstTouchTarget=null
intercepted=true
③過
④滿足mFirstTouchTarget=null, 自己消費事件
⑤過
之後的事件都同1
===事件流結束
場景三:
父view開始不攔截事件,當MOVE時開始攔截,子view消費事件。
事件流開始===
1.DOWN事件:
經過①②後:
mFirstTouchTarget=null
intercepted=false
經過③後:
mFirstTouchTarget=taget
alreadyDispatchedToNewTouchTarget=true
這時已經處理了事件
④過
⑤過
2.MOVE事件
經過①②後:
mFirstTouchTarget=target
intercepted=true
③過
④過
⑤滿足 觸摸對象不爲空,未處理過,攔截,則向target傳遞一個cancel事件,然後清除mFristTouchTarget。
3.下一個MOVE事件
經過①②後:
mFirstTouchTarget=null
intercepted=true
③過
④滿足mFirstTouchTarget=null, 自己消費事件
⑤過
之後的事件都同3
===事件流結束