注:本文涉及的demo的地址:https://github.com/absfree/TouchDispatch
1. 觸摸動作及事件序列
(1)觸摸事件的動作
觸摸動作一共有三種:ACTION_DOWN、ACTION_MOVE、ACTION_UP。當用戶手指接觸屏幕時,便產生一個動作為ACTION_DOWN的觸摸事件,此時若用戶的手指立即離開屏幕,會產生一個動作為ACTION_UP的觸摸事件;若用戶手指接觸屏幕后繼續滑動,當滑動距離超過了系統中預定義的距離常數,則產生一個動作為ACTION_MOVE的觸摸事件,系統中預定義的用來判斷用戶手指在屏幕上的滑動是否是一個ACTION_MOVE動作的這個距離常量叫做TouchSlop,可通過ViewConfiguration.get(getContext()).getScaledTouchSlop()獲取。
(2)事件序列
當用戶的手指接觸屏幕,在屏幕上滑動,又離開屏幕,這個過程會產生一系列觸摸事件:ACTION_DOWN-->若干個ACTION_MOVE-->ACTION_UP。這一系列觸摸事件即為一個事件序列。
2. 觸摸事件的分發
(1)概述
當產生了一個觸摸時間后,系統要負責把這個觸摸事件給一個View(TargetView)來處理,touch事件傳遞到TargetView的過程即為touch事件的分發。
觸摸事件的分發順序:Activity-->頂級View-->頂級View的子View-->. . .-->Target View
觸摸事件的響應順序:TargetView --> TargetView的父容器 --> . . . -->頂級View -->Activity
(2)toush事件分發的具體過程
a. Activity對touch事件的分發
當用戶手指接觸屏幕時,便產生了一個touch事件,封裝了touch事件的MotionEvent最先被傳遞給當前Activity,Activity的dispatchTouchEvent方法負責touch事件的分發。分發touch事件的實際工作由當前Activity的Window完成,而Window會將touch事件傳遞給DecorView(當前用戶界面頂級View)。Activity的dispatchTouchEvent方法代碼如下:
1 public boolean dispatchTouchEvent(MotionEvent ev) {
2 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
3 onUserInteraction();
4 }
5 if (getWindow().superDispatchTouchEvent(ev)) {
6 return true;
7 }
8 return onTouchEvent(ev);
9 }
根據以上代碼可以知道,touch事件會交由Window的superDispatchTouchEvent進行分發,若這個方法返回true,意味touch事件的分發過程結束,返回false則說明經過層層分發,沒有子View對這個事件進行處理,即所有子View的onTouchEvent方法都返回false(即這個touch事件沒有被“消耗”)。這時會調用Activity的onTouchEvent方法來處理這個touch事件。
在Window的superDispatchTouchEvent方法中,首先會把touch事件分發給DecorView,因為它是當前用戶界面的頂級View。Window的superDispatchTouchEvent方法如下:
1 public abstract boolean superDispatchTouchEvent(MotionEvent ev);
是個抽象方法,這個方法由Window的實現類PhoneWindow實現,PhoneWindow的superDispatchTouchEvent方法的代碼如下:
1 public boolean superDispatchTouchEvent(MotionEvent ev) {
2 return mDecor.superDispatchTouchEvent(event);
3 }
由以上代碼可得,PhoneWindow的superDispatchTouchEvent方法實際上是通過DecorView的superDispatchTouchEvent方法來完成自己的工作,也就是說,當前Activity的Window直接將這個touch事件傳遞給了DecorView。也就是說,目前touch事件已經經過了如下的分發:Activity-->Window-->DecorView。
b. 頂級View對touch事件的分發
經過Activity與Window的分發,現在touch事件已經被傳遞到了DecorView的dispatchTouchEvent方法中。DecorView本質上是一個ViewGroup(更具體的說是FrameLayout),ViewGroup的dispatchTouchEvent方法所做的工作可以分為如下幾個階段,第一個階段的主要代碼如下:
1 //Handle an initial down.
2 if (actionMasked == MotionEvent.ACTION_DOWN) {
3 //Throw away all previous state when starting a new touch gesture.
4 //The framework may have dropped the up or cancel event for the previous gesture due to an app switch, ANR, or some other state change.
5 cancelAndClearTouchTargets(ev);
6 resetTouchState();
7 }
第一階段的主要工作有倆:一是在第6行的resetTouchState方法中完成了對FLAG_DISALLOW_INTERCEPT標記的重置;二是第5行的cancelAndClearTouchTargets方法會清除當前MotionEvent的touch target。關於FLAG_DISALLOW_INTERCEPT標記和touch target,在下文會有相關說明。
第二階段的主要工作是決定當前ViewGroup是否攔截本次的touch事件,主要代碼如下:
1 //Check for interception.
2 final boolean intercepted;
3 if (actionMasked == MotionEvent.ACTION_DOWM || mFirstTouchTarget != null) {
4 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
5 if (!disallowIntercept) {
6 intercepted = onInterceptTouchEvent(ev);
7 ev.setAction(action); //restore action in case it was changed
8 } else {
9 intercepted = false;
10 }
11 } else {
12 //There are no touch targets and this action is not an initial down so this view group continues to intercept touches.
13 intercept =true;
14 }
由以上代碼我們可以知道,當一個touch事件被傳遞到ViewGroup時,會先判斷這個touch事件的動作是否是ACTION_DOWN,如果這個事件是ACTION_DOWN或者mFirstTouchTarget不為null,就會根據FLAG_DISALLOW_INTERCEPT標記決定是否攔截這個touch事件。那么mFirstTouchTarget是什么呢?當touch事件被ViewGroup的子View成功處理時,mFirstTouchTarget就會被賦值為成功處理touch事件的View,也就是上面提高的touch target。
總結一下上述代碼的流程:在子View不干預ViewGroup的攔截的情況下(上述代碼中的disallowIntercept為false),若當前事件為ACTION_DOWN或者mFirstTouchTarget不為空,則會調用ViewGroup的onInterceptTouchEvent方法來決定最終是否攔截此事件;否則(沒有TargetView並且此事件不是ACTION_DOWN),當前ViewGroup就攔截下此事件。 一旦ViewGroup攔截了某次touch事件,那么mFirstTouchTarget就不會被賦值,因此當再有ACTION_MOVE或是ACTION_UP傳遞到該ViewGroup時,mTouchTarget就為null,所以上述代碼第3行的條件就為false,ViewGroup會攔截下來。由此可得到的結論是:一旦ViewGroup攔截了某次事件,則同一事件序列中的剩余事件也會它默認被攔截而不會再詢問是否攔截(即不會再調用onInterceptTouchEvent)。
這里存在一種特殊情形,就是子View通過requestDisallowInterceptTouchEvent方法設置父容器的FLAG_DISALLOW_INTERCEPT為true,這個標記指示是否不允許父容器攔截,為true表示不允許。這樣做能夠禁止父容器攔截除ACTION_DOWN以外的所有touch事件。之所以不能夠攔截ACTION_DOWN事件,是因為每當ACTION_DOWN事件到來時,都會重置FLAG_DISALLOW_INTERCEPT這個標記位為默認值(false),所以每當開始一個新touch事件序列(即到來一個ACTION_DOWN動作),都會通過調用onInterceptTouchEven詢問ViewGroup是否攔截此事件。當ACTION_DOWN事件到來時,重置標記位的工作是在上面的第一階段完成的。
接下來,會進入第三階段的工作:
1 final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;
2 final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
3 TouchTarget newTouchTarget = null;
4 boolean alreadyDispatchedToNewTouchTarget = false;
5 if (!canceled && !intercepted) {
6 // 不是ACTION_CANCEL並且不攔截
7 if (actionMasked == MotionEvent.ACTION_DOWN) {
8 // 若當前事件為ACTION_DOWN則去尋找這次事件新出現的touch target
9 final int actionIndex = ev.getActionIndex(); // always 0 for down
10
11 ...
12
13 final int childrenCount = mChildrenCount;
14 if (newTouchTarget == null && childrenCount != 0) {
15 // 根據觸摸的坐標尋找能夠接收這個事件的touch target
16 final float x = ev.getX(actionIndex);
17 final float y = ev.getY(actionIndex);
18
19 final View[] children = mChildren;
20 // 遍歷所有子View
21 for (int i = childrenCount - 1; i >= 0; i--) {
22 final int childIndex = i;
23 final View child = children[childIndex];
24 // 尋找可接收這個事件並且touch事件坐標在其區域內的子View
25 if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
26 continue;
27 }
28
29 newTouchTarget = getTouchTarget(child); // 找到了符合條件的子View,賦值給newTouchTarget
30 if (newTouchTarget != null) {
31 //Child is already receiving touch within its bounds.
32 //Give it the new pointer in addition to ones it is handling.
33 newTouchTarget.pointerIdBits |= idBitsToAssign;
34 break;
35 }
36 resetCancelNextUpFlag(child);
37 // 把ACTION_DOWN事件傳遞給子組件進行處理
38 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
39 //Child wants to receive touch within its bounds.
40 mLastTouchDownTime = ev.getDownTime();
41 if (preorderedList != null) {
42 //childIndex points into presorted list, find original index
43 for (int j=0;j<childrenCount;j++) {
44 if (children[childIndex]==mChildren[j]) {
45 mLastTouchDownIndex=j;
46 break;
47 }
48 }
49 } else {
50 mLastTouchDownIndex = childIndex;
51 }
52 mLastTouchDownX = ev.getX();
53 mLastTouchDownY = ev.getY();
54 //把mFirstTouchTarget賦值為newTouchTarget,此子View成為新的touch事件的起點
55 newTouchTarget = addTouchTarget(child, idBitsToAssign);
56 alreadyDispatchedToNewTouchTarget = true;
57 break;
58 }
59 }
60 }
61 }
62 }
當ViewGroup不攔截本次事件,則touch事件會分發給它的子View進行處理,相關代碼從第21行開始:遍歷所有ViewGroup的子View,尋找能夠處理此touch事件的子View,若一個子View不在播放動畫並且touch事件坐標位於其區域內,則該子View能夠處理此touch事件,並且會把該子View賦值給newTouchTarget。
若當前遍歷到的子View能夠處理此touch事件,就會進入第38行的dispatchTransformedTouchEvent方法,該方法實際上調用了子View的dispatchTouchEvent方法。dispatchTransformedTouchEvent方法中相關的代碼如下:
1 if (child == null) {
2 handled = super.dispatchTouchEvent(event);
3 } else {
4 handled = child.dispatchTouchEvent(event);
5 }
若dispatchTransformedTouchEvent方法傳入的child參數不為null,則會調用child(即處理touch事件的子View)的dispatchTouchEvent方法。若該子View的dispatchTouchEvent方法返回true,則dispatchTransformedTouchEvent方法也會返回true,則表示成功找到了一個處理該事件的touch target,會在第55行把newTouchTarget賦值給mFirstTouchTarget(這一賦值過程是在addTouchTarget方法內部完成的),並跳出對子View遍歷的循環。若子View的dispatchTouchEvent方法返回false,ViewGroup就會把事件分發給下一個子View。
若遍歷了所有子View后,touch事件都沒被處理(該ViewGroup沒有子View或是所有子View的dispatchTouchEvent返回false),ViewGroup會自己處理touch事件,相關代碼如下:
1 if (mFirstTouchTarget == null) {
2 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
3 }
由以上代碼可知,ViewGroup自己處理touch事件時,會調用dispatchTransformedTouchEvent方法,傳入的child參數為null。根據上文的分析,傳入的chid為null時,會調用super.dispatchTouchEvent方法,即調用父類的dispatchTouchEvent方法,父類的dispatchTouchEvent方法中又包含了對onTouchEvent方法的調用,根據多態,實際上會回過頭來調用當前View的onTouchEvent方法。(關於這點的詳細說明請參考這篇文章:
c. View(不包含ViewGroup)對touch事件的處理
View的dispatchTouchEvent方法的主要代碼如下:
1 public boolean dispatchTouchEvent(MotionEvent event) {
2 boolean result = false;
3 . . .
4
5 if (onFilterTouchEventForSecurity(event)) {
6 //noinspection SimplifiableIfStatement
7 ListenerInfo li = mListenerInfo;
8 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
9 && li.mOnTouchListener.onTouch(this, event)) {
10 result = true;
11 }
12
13 if (!result && onTouchEvent(event)) {
14 result = true;
15 }
16 . . .
17 return result;
18 }
由上述代碼可知,View對touch事件的處理過程如下:由於View不包含子元素,所以它只能自己處理事件。它首先會判斷是否設置了OnTouchListener,若設置了,會調用onTouch方法,若onTouch方法返回true(表示該touch事件已經被消耗),則不會再調用onTouchEvent方法;若onTouch方法返回false或沒有設置OnTouchListener,則會調用onTouchEvent方法,onTouchEvent對touch事件進行具體處理的相關代碼如下:
1 if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
2 switch (event.getAction()) {
3 case MotionEvent.ACTION_UP:
4 boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
5 if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
6 . . .
7 if (!mHasPerformedLongPress) {
8 //This is a tap, so remove the longpress check
9 removeLongPressCallback();
10
11 //Only perform take click actions if we were in the pressed state
12 if (!focusTaken) {
13 //Use a Runnable and post this rather than calling performClick directly.
14 //This lets other visual state of the view update before click actions start.
15 if (mPerformClick == null) {
16 mPerformClck = new PeformClick();
17 }
18 if (!post(mPerformClick)) {
19 performClick();
20 }
21 }
22 }
23 . . .
24 }
25 break;
26 }
27 . . .
28 return true;
29 }
由以上代碼可知,只要View的CLICKABLE屬性和LONG_CLICKABLE屬性有一個為true(View的CLICKABLE屬性和具體View有關,LONG_CLICKABLE屬性默認為false,setOnClikListener和setOnLongClickListener會分別自動將以上倆屬性設為true),那么這個View就會消耗這個touch事件,即使這個View處於DISABLED狀態。若當前事件是ACTION_UP,還會調用performClick方法,該View若設置了OnClickListener,則performClick方法會在其內部調用onClick方法。performClick方法代碼如下:
1 public boolean performClick() {
2 final boolean result;
3 final ListenerInfo li = mListenerInfo;
4 if (li != null && li.mOnClickListener != null) {
5 playSoundEffect(SoundEffectConstants.CLICK);
6 li.mOnClickListener.onClick(this);
7 result = true;
8 } else {
9 result = false;
10 }
11 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
12 return result;
13 }
3. 結合demo詳解事件分發的各種情況
注:本文的demo參考自以下博文: http://hukai.me/android-deeper-touch-event-dispatch-process/
首先我們來看一下本文所使用的demo的布局情況:

以此為例,觸摸事件的分發及響應順序如下:
- 分發:MainActivity -> ParentLayout -> ChildLayout -> CustomButton
- 響應:CustomButton -> ChildLayotu -> ParentLayout -> MainActivity
從上面我們可以看到,分發的順序是自頂向下,而響應的順序是自底向上。我們可以把觸摸事件看做是一項任務,而MainActivity看做是一個公司的老總,CustomButton看做底層的員工,這樣來理解以下的敘述會更加容易。我們運行demo,然后在CustomButton點擊一下而后松開,然后觀察Log輸出。
在講解正常情況前,我們先來看假如把CustomButton刪去會發生什么。刪去CustomButton后的布局如下:

我們點擊一下內層的ChildLayout並松開,可以得到以下日志輸出:

這種情況是ChildLayout沒有子View,根據我們上面講解的這段代碼:
1 if (child == null) {
2 handled = super.dispatchTouchEvent(event);
3 } else {
4 handled = child.dispatchTouchEvent(event);
5 }
此時會調用super.dispatchTouchEvent(event)。對於ChildLayout來說,它的父類是LinearLayout,所以會調用LinearLayout的dispatchTouchEvent方法,我們知道dispatchTouchEvent方法中包含着對onTouchEvent方法的調用,那么在LinearLayout的dispatchTouchEvent方法中對onTouchEvent方法的調用實際上會調用ChildLayout的onTouchEvent方法(根據多態)。也就是說,當一個ViewGroup沒有子View或所有的子View都沒消耗觸摸事件時,實際上它會調用自己的onTouchEvent方法。
接下來,我們再恢復刪除的CustomButton,針對各個情況進行具體的講解。
(1)正常情況
在本例中,觸摸事件分發響應的正常情況就是CustomButton消耗了觸摸事件,然后層層向上返回true,最后到了MainAcitivity那便知道這次觸摸事件被成功消耗了。這就好比老總下發任務,經過各級經理下發到員工后,員工圓滿完成了任務,然后逐級上報最后匯報給了老總。我們來看一下日志輸出:

(2)MainActivity的dispatchTouchEvent方法中對ACTION_DOWN返回true
這種情況下,ACTION_DOWN傳到MainActivity的dispatchTouchEvent方法時,直接返回了true(被丟棄),因此沒有找到TargetView,則后續的ACITON_MOVE、ACTION_UP會直接交由MainActivity的onTouchEvent處理,而它的onTouchEvent默認會返回false(沒有能力消耗觸摸事件)。我們來看一下日志輸出:

(3)ParentLayout的dispatchTouchEvent方法中對ACTION_DOWN返回true
這種情況下,也是無法找到TargetView。由於沒有TargetView,后續的ACTION_MOVE和ACTION_UP就只能下發到ParentLayout層了,而它無法消耗觸摸事件。我們來看一下日志輸出:

需要注意的是,觸摸事件回傳時,只有ParentLayout層和Activity層能夠收到onTouchEvent的回調(假如ParentLayout還有一個父View,則該父View收不到onTouchEvent回調)。
(4)Activity的dispatchTouchEvent方法中對ACTION_MOVE返回true
這種情況下,ACTION_DOWN和ACTION_UP會被正常下發並被消耗,而ACTION_MOVE會在MainActivity即停止分發,日志如下:

注意,這里由於滑動距離未達到TouchSlop,所以並沒有產生ACTION_MOVE事件,關於對ACTION_MOVE的處理大家可以參考(2)中對ACTION_DOWN的處理(直接在MainActivity層被丟棄)。
(5)ParentLayout的onInterceptTouchEvent方法中對ACTION_DOWN返回true
這種情況下,ACTION_DOWN分發到ParentLayout時被攔截,則會交給ParentLayout的onTouchEvent處理,而由於未找到TargetView,后續的ACTION_MOVE、ACTION_DOWN事件便無從下發,在MainActivity層就會停止下發。注意到onInterceptTouchEvent方法與dispatchTouchEvent方法的區別,前者若對ACTION_DOWN返回true,則同一事件序列的后續事件便在MainActivity層終止下發;后者若對ACTION_DOWN返回true,后續ACTION_MOVE及ACTION_UP會在返回true的那層終止下發,且回傳時只有返回true的那層以及MainActivity層的onTouchEvent方法會被回掉。日志輸出如下:

(6)ChildLayout的onInterceptTouchEvent方法中對ACTION_DOWN返回true
這種情況下,ACTION_DOWN會交由ChildLayout的onTouchEvent方法處理,而它默認會返回false,於是開始回傳,經過ParentLayout、MainAcitivity均無法消費該事件。后續的事件會在MainAcitivity層便停止下發。日志如下:

(7)ChildLayout的onInterceptTouchEvent方法中對ACTION_MOVE返回true
這種情況下,ACTION_DOWN會被正常分發到CustomButton並被消費,CustomButton會成為TargetView。而ACTION_MOVE會被ChildLayout攔截,這時會產生一個ACTION_CANCEL消息(表示后續事件過不來了),這個消息會被分發給CustomButton,CustomButton會消耗它。后面若還有ACTION_MOVE會交給ChildLayout的onTouchEvent,由於無法消耗,會返回false。后續的ACTION_UP只能分發到ChildLayout。這種情況要和攔截ACTION_DOWN區分開,由於有TargetView,所以ACTION_UP能傳遞到攔截ACTION_MOVE的那層而不是終止在MainAcitivity層。日志輸出如下:
04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [dispatchTouchEvent] -> ACTION_DOWN 04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [dispatchTouchEvent] -> ACTION_DOWN 04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [onInterceptTouchEvent] -> ACTION_DOWN 04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [onInterceptTouchEvent] return false 04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [dispatchTouchEvent] -> ACTION_DOWN 04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [onInterceptTouchEvent] -> ACTION_DOWN 04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [onInterceptTouchEvent] return false 04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch D/CustomButton: [dispatchTouchEvent] -> ACTION_DOWN 04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch D/CustomButton: [onTouchEvent] -> ACTION_DOWN 04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch I/CustomButton: [onTouchEvent] return true 04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch I/CustomButton: [dispatchTouchEvent] return true 04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [dispatchTouchEvent] return true 04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [dispatchTouchEvent] return true 04-05 03:15:40.756 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [dispatchTouchEvent] return true 04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [dispatchTouchEvent] -> ACTION_MOVE 04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [dispatchTouchEvent] -> ACTION_MOVE 04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [onInterceptTouchEvent] -> ACTION_MOVE 04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [onInterceptTouchEvent] return false 04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [dispatchTouchEvent] -> ACTION_MOVE 04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [onInterceptTouchEvent] -> ACTION_MOVE, return true 04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch D/CustomButton: [dispatchTouchEvent] -> ACTION_CANCEL 04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch D/CustomButton: [onTouchEvent] -> ACTION_CANCEL 04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch I/CustomButton: [onTouchEvent] return true 04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch I/CustomButton: [dispatchTouchEvent] return true 04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [dispatchTouchEvent] return true 04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [dispatchTouchEvent] return true 04-05 03:15:40.872 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [dispatchTouchEvent] return true 04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [dispatchTouchEvent] -> ACTION_MOVE 04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [dispatchTouchEvent] -> ACTION_MOVE 04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [onInterceptTouchEvent] -> ACTION_MOVE 04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [onInterceptTouchEvent] return false 04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [dispatchTouchEvent] -> ACTION_MOVE 04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [onTouchEvent] -> ACTION_MOVE 04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [onTouchEvent] return false 04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [dispatchTouchEvent] return false 04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [dispatchTouchEvent] return false 04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [onTouchEvent] -> ACTION_MOVE 04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [onTouchEvent] return false 04-05 03:15:40.892 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [dispatchTouchEvent] return false 04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [dispatchTouchEvent] -> ACTION_MOVE 04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [dispatchTouchEvent] -> ACTION_MOVE 04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [onInterceptTouchEvent] -> ACTION_MOVE 04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [onInterceptTouchEvent] return false 04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [dispatchTouchEvent] -> ACTION_MOVE 04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [onTouchEvent] -> ACTION_MOVE 04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [onTouchEvent] return false 04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [dispatchTouchEvent] return false 04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [dispatchTouchEvent] return false 04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [onTouchEvent] -> ACTION_MOVE 04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [onTouchEvent] return false 04-05 03:15:40.908 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [dispatchTouchEvent] return false 04-05 03:15:40.924 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [dispatchTouchEvent] -> ACTION_MOVE 04-05 03:15:40.924 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [dispatchTouchEvent] -> ACTION_MOVE 04-05 03:15:40.924 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [onInterceptTouchEvent] -> ACTION_MOVE 04-05 03:15:40.924 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [onInterceptTouchEvent] return false 04-05 03:15:40.924 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [dispatchTouchEvent] -> ACTION_MOVE 04-05 03:15:40.924 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [onTouchEvent] -> ACTION_MOVE 04-05 03:15:40.924 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [onTouchEvent] return false 04-05 03:15:40.924 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [dispatchTouchEvent] return false 04-05 03:15:40.924 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [dispatchTouchEvent] return false 04-05 03:15:40.932 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [onTouchEvent] -> ACTION_MOVE 04-05 03:15:40.932 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [onTouchEvent] return false 04-05 03:15:40.932 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [dispatchTouchEvent] return false 04-05 03:15:40.940 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [dispatchTouchEvent] -> ACTION_MOVE 04-05 03:15:40.940 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [dispatchTouchEvent] -> ACTION_MOVE 04-05 03:15:40.940 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [onInterceptTouchEvent] -> ACTION_MOVE 04-05 03:15:40.940 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [onInterceptTouchEvent] return false 04-05 03:15:40.944 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [dispatchTouchEvent] -> ACTION_MOVE 04-05 03:15:40.944 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [onTouchEvent] -> ACTION_MOVE 04-05 03:15:40.944 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [onTouchEvent] return false 04-05 03:15:40.944 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [dispatchTouchEvent] return false 04-05 03:15:40.944 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [dispatchTouchEvent] return false 04-05 03:15:40.944 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [onTouchEvent] -> ACTION_MOVE 04-05 03:15:40.948 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [onTouchEvent] return false 04-05 03:15:40.948 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [dispatchTouchEvent] return false 04-05 03:15:40.972 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [dispatchTouchEvent] -> ACTION_MOVE 04-05 03:15:40.972 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [dispatchTouchEvent] -> ACTION_MOVE 04-05 03:15:40.972 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [onInterceptTouchEvent] -> ACTION_MOVE 04-05 03:15:40.972 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [onInterceptTouchEvent] return false 04-05 03:15:40.972 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [dispatchTouchEvent] -> ACTION_MOVE 04-05 03:15:40.972 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [onTouchEvent] -> ACTION_MOVE 04-05 03:15:40.976 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [onTouchEvent] return false 04-05 03:15:40.976 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [dispatchTouchEvent] return false 04-05 03:15:40.976 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [dispatchTouchEvent] return false 04-05 03:15:40.976 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [onTouchEvent] -> ACTION_MOVE 04-05 03:15:40.980 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [onTouchEvent] return false 04-05 03:15:40.980 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [dispatchTouchEvent] return false 04-05 03:15:40.980 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [dispatchTouchEvent] -> ACTION_UP 04-05 03:15:40.984 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [dispatchTouchEvent] -> ACTION_UP 04-05 03:15:40.984 30055-30055/com.yxy.zlp.touchdispatch D/ParentLayout: [onInterceptTouchEvent] -> ACTION_UP 04-05 03:15:40.984 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [onInterceptTouchEvent] return false 04-05 03:15:40.984 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [dispatchTouchEvent] -> ACTION_UP 04-05 03:15:40.984 30055-30055/com.yxy.zlp.touchdispatch D/ChildLayout: [onTouchEvent] -> ACTION_UP 04-05 03:15:40.988 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [onTouchEvent] return false 04-05 03:15:40.988 30055-30055/com.yxy.zlp.touchdispatch I/ChildLayout: [dispatchTouchEvent] return false 04-05 03:15:40.988 30055-30055/com.yxy.zlp.touchdispatch I/ParentLayout: [dispatchTouchEvent] return false 04-05 03:15:40.988 30055-30055/com.yxy.zlp.touchdispatch D/MainActivity: [onTouchEvent] -> ACTION_UP 04-05 03:15:40.992 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [onTouchEvent] return false 04-05 03:15:40.992 30055-30055/com.yxy.zlp.touchdispatch I/MainActivity: [dispatchTouchEvent] return false
總結一下:若某一層的dispatchTouchEvent方法對ACTION_DOWN返回true,則被認為該層能夠消耗觸摸事件,因此后續的事件會傳到該層;而某一層的onInterceptTouchEvent方法返回true的話,由於它的onTouchEvent返回false進而導致它的dispatchTouchEvent方法返回false,則該層會被認為不能消耗觸摸事件,所以后續事件只會到Activity層。
4. 參考資料
(1)《Android開發藝術探索》
