一個demo讓你完全理解Android觸摸事件的併發

注:本文涉及的demo的地址:https://github.com/absfree/TouchDispatch

1. 觸摸動做及事件序列

(1)觸摸事件的動做android

    觸摸動做一共有三種:ACTION_DOWN、ACTION_MOVE、ACTION_UP。當用戶手指接觸屏幕時,便產生一個動做爲ACTION_DOWN的觸摸事件,此時若用戶的手指當即離開屏幕,會產生一個動做爲ACTION_UP的觸摸事件;若用戶手指接觸屏幕後繼續滑動,當滑動距離超過了系統中預約義的距離常數,則產生一個動做爲ACTION_MOVE的觸摸事件,系統中預約義的用來判斷用戶手指在屏幕上的滑動是不是一個ACTION_MOVE動做的這個距離常量叫作TouchSlop,可經過ViewConfiguration.get(getContext()).getScaledTouchSlop()獲取。git

(2)事件序列github

    當用戶的手指接觸屏幕,在屏幕上滑動,又離開屏幕,這個過程會產生一系列觸摸事件:ACTION_DOWN-->若干個ACTION_MOVE-->ACTION_UP。這一系列觸摸事件即爲一個事件序列。app

 

2. 觸摸事件的分發

(1)概述佈局

    當產生了一個觸摸時間後,系統要負責把這個觸摸事件給一個View(TargetView)來處理,touch事件傳遞到TargetView的過程即爲touch事件的分發。post

    觸摸事件的分發順序:Activity-->頂級View-->頂級View的子View-->. . .-->Target View動畫

    觸摸事件的響應順序:TargetView --> TargetView的父容器 --> . . . -->頂級View -->Activitythis

 

(2)toush事件分發的具體過程spa

  a. Activity對touch事件的分發rest

    當用戶手指接觸屏幕時,便產生了一個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開發藝術探索》

(2)Touch事件分發響應機制

相關文章
相關標籤/搜索