Android Touch事件傳遞機制詳解 上



       最近總是遇到關於Android Touch事件的問題,如:滑動沖突的問題,以前也花時間學習過Android Touch事件的傳遞機制,可以每次用起來的時候總是忘記了,索性自己總結一下寫篇文章避免以后忘記了,其實網上關於Touch事件的傳遞的文章真的很多,但是很少有系統性的,都是寫了一個簡單的demo運行了一下,對於我們了解Android Touch事件基本上沒有任何幫助。

       今天我打算從源碼的角度來分析一下Touch事件的傳遞機制。在了解Touch事件之前,最好了解下Android中窗口的創建過程,這樣對於Android窗口的整體結構和事件的傳遞過程會了解更深。

       我就把事件的始點定在PhoneWindow中的DecorView吧,至於是誰把事件傳遞給DecorView的我們先不用去關心它。(如果想深入研究,請閱讀我的另外一篇文章Android中按鍵事件傳遞機制)我們只需要知道它的上家是通過dispatchTouchEvent方法將事件分發給DecorView就行了,我進入到該方法瞧瞧究竟。

在閱讀之前最好閱讀Android窗口創建過程

 

[java]  view plain copy print?在CODE上查看代碼片派生到我的代碼片
  1. @Override  
  2.         public boolean dispatchTouchEvent(MotionEvent ev)   
  3.     {  
  4.         //該Callback就是該DecorView附屬的Activity,可以看我的另外一篇文章《Android中窗口的創建過程》  
  5.             final Callback cb = getCallback();  
  6.         //如果cb!=null && mFeatureId<0 就執行Activity中的dispatchTouchEvent方法,對於應用程序窗口 <span >         </span>    //這兩個條件一般是滿足的  
  7.             return cb != null && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super  
  8.                     .dispatchTouchEvent(ev);  
  9.         }  

在DecorView中事件通過dispatchTouchEvent方法被分發到了Activity中,相信Activity對於每個Android開發者都不會陌生吧,那我們就進入Activity的dispatchTouchEvent方法中。

 

[java]  view plain copy print?在CODE上查看代碼片派生到我的代碼片
  1. public boolean dispatchTouchEvent(MotionEvent ev) {  
  2.        if (ev.getAction() == MotionEvent.ACTION_DOWN) {  
  3.            onUserInteraction();  
  4.        }  
  5. //getWindow返回什么?如果閱讀過我的《Android中窗口創建過程》的都知道就是PhoneWindow,如果PhoneWindow中的superDispatchTouchEvent方法返回了true,  
  6. //那么該Touch事件就被PhoneWindow給消費掉了,不會再繼續傳遞,如果返回false,那么就會執行Activity的onTouchEvent方法  
  7.        if (getWindow().superDispatchTouchEvent(ev)) {  
  8.            return true;  
  9.        }  
  10.        return onTouchEvent(ev);  
  11.    }  

進入PhoneWindow中的superDispatchTouchEvent方法:

 

[java]  view plain copy print?在CODE上查看代碼片派生到我的代碼片
  1. @Override  
  2.     public boolean superDispatchTouchEvent(MotionEvent event) {  
  3.         //mDecor是一個DecorView類型變量  
  4.         return mDecor.superDispatchTouchEvent(event);  
  5.     }  


進入DecorView中的superDispatchTouchEvent方法:

[java]  view plain copy print?在CODE上查看代碼片派生到我的代碼片
  1. public boolean superDispatchTouchEvent(MotionEvent event) {  
  2.             //直接調用父類的dispatchTouchEvent方法  
  3.             return super.dispatchTouchEvent(event);  
  4.         }  


走到這里我們先暫停一下,會看一下DecorView類的dispatchTouchEvent方法,如果callBack不為空,那么調用CallBack的dispatchTouchEvent方法,否則調用super.dispatchTouchEvent方法,但是在CallBack不為空的條件下最中也是調用到了super.dispatchTouchEvent方法,那么它的super是哪個那,我們繼續往下看:
通過源碼我們可以看到DecorView是繼承自FrameLayout。所以事件最終是傳遞到了FrameLayout的dispatchTouchEvent中,FrameLayout中的此方法是繼承自ViewGroup的,我們直接到ViewGroup中查看此方法吧:

 

 

[java]  view plain copy print?在CODE上查看代碼片派生到我的代碼片
  1. @Override  
  2.     public boolean dispatchTouchEvent(MotionEvent ev) {  
  3.         final int action = ev.getAction();  
  4.         final float xf = ev.getX();  
  5.         final float yf = ev.getY();  
  6.         final float scrolledXFloat = xf + mScrollX;  
  7.         final float scrolledYFloat = yf + mScrollY;  
  8.         final Rect frame = mTempRect;  
  9.         //可以通過requestDisallowInterceptTouchEvent方法來設置該變量的值,通常是false  
  10.         boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
  11.   
  12.         if (action == MotionEvent.ACTION_DOWN) {  
  13.             if (mMotionTarget != null) {  
  14.                 // this is weird, we got a pen down, but we thought it was  
  15.                 // already down!  
  16.                 // XXX: We should probably send an ACTION_UP to the current  
  17.                 // target.  
  18.                 mMotionTarget = null;  
  19.             }  
  20.             // If we're disallowing intercept or if we're allowing and we didn't  
  21.             // intercept  
  22.             //onInterceptTouchEvent在默認情況下是返回false的,所以這里通常是可以進去的  
  23.             if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
  24.                 // reset this event's action (just to protect ourselves)  
  25.                 ev.setAction(MotionEvent.ACTION_DOWN);  
  26.                 // We know we want to dispatch the event down, find a child  
  27.                 // who can handle it, start with the front-most child.  
  28.                 final int scrolledXInt = (int) scrolledXFloat;  
  29.                 final int scrolledYInt = (int) scrolledYFloat;  
  30.                 final View[] children = mChildren;  
  31.                 final int count = mChildrenCount;  
  32.                 //遍歷ViewGroup的孩子,如果觸摸點在某一個子View中,則調用在子View的dispatchTouchEvent  
  33.                 for (int i = count - 1; i >= 0; i--) {  
  34.                     final View child = children[i];  
  35.                     if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
  36.                             || child.getAnimation() != null) {  
  37.                         child.getHitRect(frame);  
  38.                         if (frame.contains(scrolledXInt, scrolledYInt)) {  
  39.                             // offset the event to the view's coordinate system  
  40.                             final float xc = scrolledXFloat - child.mLeft;  
  41.                             final float yc = scrolledYFloat - child.mTop;  
  42.                             ev.setLocation(xc, yc);  
  43.                             child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  44.                 //調用了某一個子View 的dispatchTouchEvent ,如果這個子View 的dispatchTouchEvent返回true,那么意味着這個事件  
  45.                 //已經被這個子View消費了,不會繼續傳遞  
  46.                             if (child.dispatchTouchEvent(ev))  {  
  47.                                 // Event handled, we have a target now.  
  48.                                 mMotionTarget = child;  
  49.                                 return true;  
  50.                             }  
  51.                             // The event didn't get handled, try the next view.  
  52.                             // Don't reset the event's location, it's not  
  53.                             // necessary here.  
  54.                         }  
  55.                     }  
  56.                 }  
  57.             }  
  58.         }  
  59.   
  60.         boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
  61.                 (action == MotionEvent.ACTION_CANCEL);  
  62.   
  63.         if (isUpOrCancel) {  
  64.             // Note, we've already copied the previous state to our local  
  65.             // variable, so this takes effect on the next event  
  66.             mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
  67.         }  
  68.   
  69.         // The event wasn't an ACTION_DOWN, dispatch it to our target if  
  70.         // we have one.  
  71.         final View target = mMotionTarget;  
  72.         //對於一個Action_down事件,如果走到了這里,說明所有的子View 都沒有消費掉這個事件,那么它就調用父類的  
  73.         //的dispatchTouchEvnet方法,ViewGroup的父類就是View  
  74.         if (target == null) {  
  75.             // We don't have a target, this means we're handling the  
  76.             // event as a regular view.  
  77.             ev.setLocation(xf, yf);  
  78.             if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
  79.                 ev.setAction(MotionEvent.ACTION_CANCEL);  
  80.                 mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  81.             }  
  82.             return super.dispatchTouchEvent(ev);  
  83.         }  
  84.   
  85.         // if have a target, see if we're allowed to and want to intercept its  
  86.         // events  
  87.         if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
  88.             final float xc = scrolledXFloat - (float) target.mLeft;  
  89.             final float yc = scrolledYFloat - (float) target.mTop;  
  90.             mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  91.             ev.setAction(MotionEvent.ACTION_CANCEL);  
  92.             ev.setLocation(xc, yc);  
  93.             if (!target.dispatchTouchEvent(ev)) {  
  94.                 // target didn't handle ACTION_CANCEL. not much we can do  
  95.                 // but they should have.  
  96.             }  
  97.             // clear the target  
  98.             mMotionTarget = null;  
  99.             // Don't dispatch this event to our own view, because we already  
  100.             // saw it when intercepting; we just want to give the following  
  101.             // event to the normal onTouchEvent().  
  102.             return true;  
  103.         }  
  104.   
  105.         if (isUpOrCancel) {  
  106.             mMotionTarget = null;  
  107.         }  
  108.   
  109.         // finally offset the event to the target's coordinate system and  
  110.         // dispatch the event.  
  111.         final float xc = scrolledXFloat - (float) target.mLeft;  
  112.         final float yc = scrolledYFloat - (float) target.mTop;  
  113.         ev.setLocation(xc, yc);  
  114.   
  115.         if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
  116.             ev.setAction(MotionEvent.ACTION_CANCEL);  
  117.             target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  118.             mMotionTarget = null;  
  119.         }  
  120.   
  121.         return target.dispatchTouchEvent(ev);  
  122.     }  


剛才在看ViewGroup的dispatchTouchEvent方法時,我們看到了一個方法onInterceptTouchEvent,這個方法是干什么的呢,我們先看看他都干了什么吧

 

[java]  view plain copy print?在CODE上查看代碼片派生到我的代碼片
  1. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  2.         return false;  
  3.     }  


發現里面就是返回了一個false, 通過方法名字我們就可以知道該方法的作用,是否阻止TouchEvent的傳遞,默認是false 也就是不會阻止。

現在總結一下ViewGroup的dispatchTouchEvnet的邏輯 ,畢竟這個方法有些復雜:
1、如果disallowIntercept|| !onInterceptTouchEvent(),那么事件才可以繼續傳遞下去,否則直接調用該ViewGroup的父類的dispatchTouchEvent,也就是View的dispatchTouchEvent.
2、依次遍歷ViewGroup的所有子View,將事件傳遞個子View,如果某一個子View處理了該事件,並且返回true,那么事件結束,停止傳遞
3、如果所有的子View沒有消費掉這個事件,那么就調用View的dispatchTouchEvent

對於任何一款Android應用,展現給用戶最上面的通常就是一個View,如Button,ImageView等等,也就是說一些觸摸事件最終都是傳遞給了這個控件,如果控件消費了這些事件,那么就停止傳遞了,如果沒有消費,那么就交給控件所屬ViewGroup的onTouchEvnet處理,我們就看看View的dispatchTouchEvent方法吧

 

[java]  view plain copy print?在CODE上查看代碼片派生到我的代碼片
  1. public boolean dispatchTouchEvent(MotionEvent event) {  
  2.         if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
  3.                 mOnTouchListener.onTouch(this, event)) {  
  4.             return true;  
  5.         }  
  6.         return onTouchEvent(event);  
  7.     }  


View的這個方法非常簡單,首先判斷mTouchListener是否為空,並且這個View是否Eneable,如果都滿足,那么首先調用mOnTouchListener.onTouch方法,如果onTouch方法返回true,那么就是說這個View消費了該事件,直接返回true,如果onTouch返回false,那么就會調用onTouchEvnet方法,這個mOnTouchListener是什么?

 

[java]  view plain copy print?在CODE上查看代碼片派生到我的代碼片
  1. public void setOnTouchListener(OnTouchListener l) {  
  2.        mOnTouchListener = l;  
  3.    }  


看了這個就明白了吧,就是我們通過setOnTouchListener賦值的,另外我們還需要注意一點就是這個onTouch是在onTouchEvent方法之前執行的哦。
最后我們就看看這個View的onTouchEvnet吧

 

[java]  view plain copy print?在CODE上查看代碼片派生到我的代碼片
  1. public boolean onTouchEvent(MotionEvent event) {  
  2.         final int viewFlags = mViewFlags;  
  3.         //(A)  
  4.         if ((viewFlags & ENABLED_MASK) == DISABLED) {  
  5.             // A disabled view that is clickable still consumes the touch  
  6.             // events, it just doesn't respond to them.  
  7.             return (((viewFlags & CLICKABLE) == CLICKABLE ||  
  8.                     (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
  9.         }  
  10.   
  11.         if (mTouchDelegate != null) {  
  12.             if (mTouchDelegate.onTouchEvent(event)) {  
  13.                 return true;  
  14.             }  
  15.         }  
  16.         //(B)  
  17.         if (((viewFlags & CLICKABLE) == CLICKABLE ||  
  18.                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
  19.             switch (event.getAction()) {  
  20.                 case MotionEvent.ACTION_UP:  
  21.                     boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
  22.                     if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
  23.                         // take focus if we don't have it already and we should in  
  24.                         // touch mode.  
  25.                         boolean focusTaken = false;  
  26.                         if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
  27.                             focusTaken = requestFocus();  
  28.                         }  
  29.   
  30.                         if (!mHasPerformedLongPress) {  
  31.                             // This is a tap, so remove the longpress check  
  32.                             removeLongPressCallback();  
  33.   
  34.                             // Only perform take click actions if we were in the pressed state  
  35.                             if (!focusTaken) {  
  36.                                 // Use a Runnable and post this rather than calling  
  37.                                 // performClick directly. This lets other visual state  
  38.                                 // of the view update before click actions start.  
  39.                                 if (mPerformClick == null) {  
  40.                                     mPerformClick = new PerformClick();  
  41.                                 }  
  42.                                 //(C)  
  43.                                 if (!post(mPerformClick)) {  
  44.                                     performClick();  
  45.                                 }  
  46.                             }  
  47.                         }  
  48.   
  49.                         if (mUnsetPressedState == null) {  
  50.                             mUnsetPressedState = new UnsetPressedState();  
  51.                         }  
  52.   
  53.                         if (prepressed) {  
  54.                             mPrivateFlags |= PRESSED;  
  55.                             refreshDrawableState();  
  56.                             postDelayed(mUnsetPressedState,  
  57.                                     ViewConfiguration.getPressedStateDuration());  
  58.                         } else if (!post(mUnsetPressedState)) {  
  59.                             // If the post failed, unpress right now  
  60.                             mUnsetPressedState.run();  
  61.                         }  
  62.                         removeTapCallback();  
  63.                     }  
  64.                     break;  
  65.   
  66.                 case MotionEvent.ACTION_DOWN:  
  67.                     if (mPendingCheckForTap == null) {  
  68.                         mPendingCheckForTap = new CheckForTap();  
  69.                     }  
  70.                     mPrivateFlags |= PREPRESSED;  
  71.                     mHasPerformedLongPress = false;  
  72.                     postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
  73.                     break;  
  74.   
  75.                 case MotionEvent.ACTION_CANCEL:  
  76.                     mPrivateFlags &= ~PRESSED;  
  77.                     refreshDrawableState();  
  78.                     removeTapCallback();  
  79.                     break;  
  80.   
  81.                 case MotionEvent.ACTION_MOVE:  
  82.                     final int x = (int) event.getX();  
  83.                     final int y = (int) event.getY();  
  84.   
  85.                     // Be lenient about moving outside of buttons  
  86.                     int slop = mTouchSlop;  
  87.                     if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
  88.                             (y < 0 - slop) || (y >= getHeight() + slop)) {  
  89.                         // Outside button  
  90.                         removeTapCallback();  
  91.                         if ((mPrivateFlags & PRESSED) != 0) {  
  92.                             // Remove any future long press/tap checks  
  93.                             removeLongPressCallback();  
  94.   
  95.                             // Need to switch from pressed to not pressed  
  96.                             mPrivateFlags &= ~PRESSED;  
  97.                             refreshDrawableState();  
  98.                         }  
  99.                     }  
  100.                     break;  
  101.             }  
  102.             //(D)  
  103.             return true;  
  104.         }  
  105.   
  106.         return false;  
  107.     }  


這個方法也是相當的復雜啊,但是我們沒有必要每一行都看,我們只需要挑重點看就Ok了。
請細看我標了 A B C D的四個地方,在A處,如果該View是Disable的,那么只要該View是clickable或者longclickable的,那么這個事件就被該View消費掉了,返回true
在看B 和 D,兩處,如果該View是clickable或者longclickable的,那么D出總是返回true,也就是說事件一直被消費,至於C處我主要是說明的是View的onClick事件是在ACTION_UP中觸發的。

學習到這里,我又需要總結一下:
如果我們觸摸的一個View是clickable或者longclickable的,那么這個事件肯定會被這個View消費掉(當然前提是你沒有改寫它所在ViewGroup的onInterceptTouchEvent方法,如果你改寫此方法返回true,那么View是無法接收到這個事件的)

我們現在還要思考一個問題,如果這個View沒有消費掉這個事件,這個事件最終拋向何方?
還記得前面我說過ViewGroup的dispatchTouchEvent方法嗎,如果它的所有的子View沒有處理掉該事件,那么調用的是父類View的dispatchTouchEvnet方法,從而執行到了該ViewGroup的onTouch和onTouchEvent方法。

那如果ViewGroup也沒有處理該事件呢,這里就要分兩種情況啦:
1、如果這個ViewGroup不是DecorView,也就是說他的父View就是一個普通的ViewGroup(如LinearLayout里面放置一個LinearLayout),那么和上面子View沒有處理掉消息有點類似,調用父類的onTouch和onTouchEvent方法
2、如果這個ViewGroup就是DecorView,那么就調用到了Activity的onTouchEvnet方法(此時沒有onTouch方法)。

今天就先寫到這里吧,后面我回用一個簡單的Demo和一個簡單的滑動沖突問題在深入學習TouchEvnet事件的。如果哪里沒有寫清楚的 ,歡迎拍磚。。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM