整個觸摸事件牽涉到的是,Activity,View,ViewGroup三者的傳遞機制。
這個觸摸事件就是從外層往內層一層層的傳遞。
整個傳遞機制,分為3個步驟:分發,攔截,和消費。
1. 觸摸事件的類型
事件類型是MotionEvent類:看下最新的sdk29的源碼,一堆的Action,我們常用的其實就3個,ACTION_DOWN,ACTION_MOVE,ACTION_UP。
Down和Up事件,是觸摸過程中一定會發生的事件。Move事件看具體的情況。
2.事件傳遞的順序
從外層往內層一層層的傳遞,外層是那個,內層是那個?
我們從android系統的角度看。事件首先傳給的肯定是Activity,因為Activity的UI布局,事物邏輯,作為底層來說,不知道,也並不關心。
所以只要把事件送到Activity層,就可以。
同樣的道理,事件先送到ViewGroup,然后在送到View。這樣從外層到內層的過程。
其實每一層都會對觸摸事件有響應,這個事件是有具體的業務邏輯決定的,所以我們需要一些操作來控制這個事件的過程。
比如點擊事件,假設點擊了某個按鈕,這樣它的父ViewGroup要不要處理,頁面要不要處理,從表面來看他們都被點擊了,從實際人們的經驗來看,
就是View被點擊了,不需要ViewGroup和Activtiy后面2個事件觸發。
Activity->ViewGroup->ViewGroup...->View 整個事件流程傳遞就是這樣。
View->ViewGroup...->ViewGroup->Activity 這個就是事件的消費過程。
3.事件傳遞的3個階段
所以android把觸摸事件傳遞分為3個步驟:分發,攔截,和消費。
3.1 分發
分發:事件分發對應着dispatchTouchEvent方法,在Android中,所有的觸摸事件都是這個方法來分發的。
public boolean dispatchTouchEvent(MotionEvent ev)
3.1.1 Activity分發
分發是觸摸事件首先到達。
- 返回true,表示事件被消耗掉。不需要分發下去。消費了事件,后續的事件會繼續傳遞到這里。
- 返回false,表示事件被廢棄掉。因為dispatchTouchEvent是總入口,所以就算是false,后續事件還會出現。
- 返回值是super.dispatchTouchEvent,事件繼續傳遞下去,這個是整個事件傳遞的中樞,從源碼看,一切從這來開始,也從這里結束。
如果view hierarchy都不處理這個touch事件,就由Activity的onTouch事件來處理。可見上面第二節的結論是正確的。最先接收,最后處理。
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
3.1.2VIewGroup分發
- 返回true,表示事件被消耗掉。不需要分發下去。消費了事件,后續的事件會繼續傳遞到這里。
- 返回false,表示事件被廢棄掉。后續的其他事件不會被傳遞到這里。
- 返回值是super.dispatchTouchEvent,事件繼續傳遞下去,這個是整個事件傳遞的中樞,從源碼看,ViewGroup的分發流程分以下幾步
有攔截,攔截了touch事件,就走ViewGroup自己的onTouchEvent。
沒有攔截,就找到觸摸點的位置,上面是否有View,有就調view.dispatchTouchEvent,如果沒有,就直接交給ViewGroup處理。
這里還有一點,如果action_down已經處理了,后面action_up 和 action_move都使用相同的處理方式。
3.1.3 View的分發
View的分發,也許會覺得奇怪,分發給誰?
從源碼看,2個途徑,onTouch listener,或者onTouchEvent。前者優先級必后者高。
- 返回true,表示事件被消耗掉。不需要分發下去。消費了事件,后續的事件會繼續傳遞到這里。並且這里會認為事件被View消費掉了,所以上層的VIewGroup不會收到要處理onTouchEvent的事件。
- 返回false,表示事件被廢棄掉。后續的其他事件不會被傳遞到這里。而且可以理解為,我這個View不想處理這個事件,所以ViewGroup會觸發onTouchEvent事件。
- 返回值是super.dispatchTouchEvent
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;
}
onTouch是否處理,return true,就表面被第三方處理了,不需要onTouchEvent來處理。不然就是onTouchEvent處理。
3.2 攔截
事件攔截對應onInterceptTouchEvent。事件是從外向內傳遞的,當事件在分發的過程中,要不要攔截,
public boolean onInterceptTouchEvent(MotionEvent ev)
有些情況下需要子View來響應,有些情況需要當前ViewGroup來處理。這個方法Activity沒有,只有ViewGroup才有。
這個簡單來說就是,是否需要分發事件給到子View,true就是不分發。
3.3 消費
其實dispatchTouchEvent也有消費的概念在里面。
如果說分發的流程,是從外層到內層,
消費的流程正好相反,從內層到外層。
public boolean onTouchEvent(MotionEvent event)
- 返回true,消費事件,不在向外層傳遞
-返回false,不消費事件,向外層傳遞
-返回spuer.onTouchEvent,這里會判斷是否是click事件。只有這個寫法才會觸發click事件。
4. 流程圖
5.總結
流程很復雜,每個部分返回不同的true/fase/super都會產生不同的流程結果。
事件分發流程:
從外層到內層分發,
從內層到外層消費。
dispatchTouchEvent 和onTouchEvent,返回true,代表由自己處理這個事情。阻斷后面的流程。
dispatchTouchEvent 和onTouchEvent,返回false,表示不由自己來處理這個事件,流程交由上一層級來處理。
dispatchTouchEvent 和onTouchEvent,返回spuer,就是上面的流程。
更多內容:demanmath
公共號: