天天看點

關于android touch事件源代碼分析

   本篇部落格描述我閱讀android源代碼的了解,在此做一個筆記.

   關于android的touch事件,也就是觸摸屏事件,最早的調用在mView.dispatchTouchEvent(),該函數是在ViewRoot調用的,關于為何事件處理轉到ViewRoot來,有如下過程,android 所有的鍵盤,觸摸事件的處理都需要最終查問Window類,因為Window類記錄了窗體的資訊,我們能夠根據這個查找到需要響應該事件的View,android對此異步處理的,通過Handler将此消息發給相應的View,而這個任務由ViewRoot來完成,ViewRoot本身就是一個Handler對象,并且每一個窗體都有一個ViewRoot對象,以便異步處理消息,因為對消息的處理有多個不同的線程協同完成。

   上面說道mView.dispathTouchEvent(),那麼mView是一個什麼樣的對象呢?根據柯元旦老師的<android核心剖析>,mView分兩種情況:對于應用視窗而言,mView是一個PhoneWindow中的DecorView類型,對于非應用視窗而言,mView是一般的ViewGroup類型。關于DecorView類型,他是所有應用視窗的view hierarchy的root,也就是view 樹的根,它繼承FrameLayout。在非應用視窗,mView.dispatchTouchEvent()就是等同于ViewGroup.dispatchTouchEvent()。對于DecorView類型的調用我們着重探讨.

   DecorView.dispathTouchEvent(),首先判斷是否存在Callback對象,Callback 對象其實就是Activity累。如果沒有Callback對象,(else)直接調用DecorView基類ViewGroup的dispatchOnTouchEvent()。一般都是存在CallBack對象的,我們可以看Activity的dispatchOnTouchEvent().

//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 c//all this implementation for touch screen events that should be handled normally.
//Parameters:
//ev The touch screen event.
//Returns:
//boolean Return true if this event was consumed.
2081
2082    public boolean dispatchTouchEvent(MotionEvent ev) {
2083        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
2084            onUserInteraction();
2085        }
2086        if (getWindow().superDispatchTouchEvent(ev)) {
2087            return true;
2088        }
2089        return onTouchEvent(ev);
2090    }           
這是Activity 的實作方法,但是我們可以重載這個方法,是以我們可以将touch事件全權交由Activity處理,不傳遞給任何的View,看代碼2082開始,如果是            
ACTION_DOWN事件,有一個Callback onUserInteraction(),告訴我們頁面開始和使用者互動了(該方法是沒有傳回值的),是以第2086行,Activity将事件交給Window(getWindow()傳回對象)處理,可以去看Window類的方法superDispatchTouchEvent,可以看到最終是調用到ViewGroup的dispatchOnTouchEvent().是以從這裡看出,有沒有CallBack都是會調用ViewGroup的dispatchOnTouchEvent(),但是Activity給我一個統一管理觸摸事件的機會,我們可以重載Activit 的dispatchTouchEvent方法,自己處理觸摸事件。           
okay,以上說道ViewGroup的dispatchTouchEvent方法,看android源代碼。            
@Override
781     public boolean dispatchTouchEvent(MotionEvent ev) {
782         final int action = ev.getAction();
783         final float xf = ev.getX();
784         final float yf = ev.getY();
785         final float scrolledXFloat = xf + mScrollX;
786         final float scrolledYFloat = yf + mScrollY;
787         final Rect frame = mTempRect;
788 
789         boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
790 
791         if (action == MotionEvent.ACTION_DOWN) {
792             if (mMotionTarget != null) {
793                 // this is weird, we got a pen down, but we thought it was
794                 // already down!
795                 // XXX: We should probably send an ACTION_UP to the current
796                 // target.
797                 mMotionTarget = null;
798             }
799             // If we're disallowing intercept or if we're allowing and we didn't
800             // intercept
801             if (disallowIntercept || !onInterceptTouchEvent(ev)) {
802                 // reset this event's action (just to protect ourselves)
803                 ev.setAction(MotionEvent.ACTION_DOWN);
804                 // We know we want to dispatch the event down, find a child
805                 // who can handle it, start with the front-most child.
806                 final int scrolledXInt = (int) scrolledXFloat;
807                 final int scrolledYInt = (int) scrolledYFloat;
808                 final View[] children = mChildren;
809                 final int count = mChildrenCount;
810                 for (int i = count - 1; i >= 0; i--) {
811                     final View child = children[i];
812                     if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
813                             || child.getAnimation() != null) {
814                         child.getHitRect(frame);
815                         if (frame.contains(scrolledXInt, scrolledYInt)) {
816                             // offset the event to the view's coordinate system
817                             final float xc = scrolledXFloat - child.mLeft;
818                             final float yc = scrolledYFloat - child.mTop;
819                             ev.setLocation(xc, yc);
820                             if (child.dispatchTouchEvent(ev))  {
821                                 // Event handled, we have a target now.
822                                 mMotionTarget = child;
823                                 return true;
824                             }
825                             // The event didn't get handled, try the next view.
826                             // Don't reset the event's location, it's not
827                             // necessary here.
828                         }
829                     }
830                 }
831             }
832         }
833 
834         boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
835                 (action == MotionEvent.ACTION_CANCEL);
836 
837         if (isUpOrCancel) {
838             // Note, we've already copied the previous state to our local
839             // variable, so this takes effect on the next event
840             mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
841         }
842 
843         // The event wasn't an ACTION_DOWN, dispatch it to our target if
844         // we have one.
845         final View target = mMotionTarget;
846         if (target == null) {
847             // We don't have a target, this means we're handling the
848             // event as a regular view.
849             ev.setLocation(xf, yf);
850             return super.dispatchTouchEvent(ev);
851         }
852 
853         // if have a target, see if we're allowed to and want to intercept its
854         // events
855         if (!disallowIntercept && onInterceptTouchEvent(ev)) {
856             final float xc = scrolledXFloat - (float) target.mLeft;
857             final float yc = scrolledYFloat - (float) target.mTop;
858             ev.setAction(MotionEvent.ACTION_CANCEL);
859             ev.setLocation(xc, yc);
860             if (!target.dispatchTouchEvent(ev)) {
861                 // target didn't handle ACTION_CANCEL. not much we can do
862                 // but they should have.
863             }
864             // clear the target
865             mMotionTarget = null;
866             // Don't dispatch this event to our own view, because we already
867             // saw it when intercepting; we just want to give the following
868             // event to the normal onTouchEvent().
869             return true;
870         }
871 
872         if (isUpOrCancel) {
873             mMotionTarget = null;
874         }
875 
876         // finally offset the event to the target's coordinate system and
877         // dispatch the event.
878         final float xc = scrolledXFloat - (float) target.mLeft;
879         final float yc = scrolledYFloat - (float) target.mTop;
880         ev.setLocation(xc, yc);
881 
882         return target.dispatchTouchEvent(ev);
883     }      
該方法的内部操作分幾部:           
1.将布局坐标轉換為視圖坐标,意思是将視圖目前的坐标轉成視圖顯示内容的坐标。這個不具體分析了,有興趣的可以檢視相關内容.           
2.目前事件是否是MotionEvent.ACTION_DOWN,代碼在791處開始,如果是需要查處到底是那個子view接收該事件。詳細見810行開始,如果找到了相應的子視圖,則調用子視圖的dispatchTouchEvent,那這裡需要注意了,如果該子視圖是ViewGroup類型,那麼會遞歸調用相應的view的dispatchOnTouch(),直到該view不是ViewGroup對象,因為regular view(非ViewGroup)對象的dispatchOnTouch會調用onTouchEvent做最終的傳回值,關于這些不同,下面還會有講述.見820行如果child.dispatchTouch()傳回true,有一個重要的變量指派了:            
mMotionTarget = child;return true;           
并且傳回了true,ACTION_DOWN事件被處理完了。但是在ACTION_DOWN中并沒有傳回true呢?那麼mMotionTarget==null,看846行,那麼該ViewGroup就以regular view的方式處理該事件。關鍵是850處  return super.dispatchTouchEvent(ev);super?那就是View類,那我們看View的dispatchOnTouchEvent,看看跟ViewGroup有什麼不同.代碼:           
3703
3704    public boolean dispatchTouchEvent(MotionEvent event) {
3705        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
3706                mOnTouchListener.onTouch(this, event)) {
3707            return true;
3708        }
3709        return onTouchEvent(event);
3710    }           
注意這裡java的多态性,如果我們在ViewGroup中設定了(setOnTouchListener()),并且該ViewGroup enabled,我們先調用listener 的onTouch,如果onTouch傳回true,該方法直接傳回true,但是不是我們需要調用onTouchEvent()(注意Java的多态性,我們可以重載onTouchEvent).是以我們setOnTouchListener 是先于onTouchEvent調用的(一般情形,因為有其他條件).但是我們需要思考什麼情況下我們會調用到這一步?是mMotionTarget==null的空,那什麼情況下會等于空?第一是我們在ACTION_DOWN中child并沒有處理ACTION_DOWN,那麼還有另外一種狀況看855,在mMotionTarget不等于null的情形下,意思是ACTION_DOWN事件得到處理,而後我們處理其他的事件,比如ACTION_MOVE,ACTION_UP,目前ViewGroup可以截斷該事件(但是在ACTION_DOWN中并沒有截斷該事件,不然mMotionTarget==null),如果發生截斷的情形下,在865行,mMotionTarget = null;然後直接傳回true,并沒有調用super.dispathOnTouchEvent(),意思是截斷了事件在onInterceptTouchEvent中。最普遍的情況下是ACTION_DOWN事件得到處理,并且不被VIewGroup截斷,那麼 return target.dispatchTouchEvent(ev);交由child對剩餘的事件處理.
綜上,可以歸納android對于ViewGroup對dispatchTouchEvent的處理:架設有A,B,C,D四個View,A是B的parent,B是C的parent,C是D的parent,D是純View,不是ViewGroup對象。           
第一ACTION_DOWN事件:           
1.A截取了事件,那麼A中的mMotionTarget==null,return A的super.dispatchTouchEvent(ev);若A中有OnTouchListener并且onTouch傳回true,A的dispatchTouchEvent直接傳回true,并不掉用A的onTouchEvent,若無監聽器,或者onTouch傳回false,則需要調用onTouchEvent().           
2.A未截取該事件,但是A的child.dispatchTouchEvent傳回false,情況同1.           
3.A未截取該事件,A的child.dispatchTouchEvent傳回true,則ACTION_DOWN事件處理完畢.           
第二ACTION_MOVE事件:           
1.若ACTION_DOWN事件處理方式是1,2,處理同ACTION_DOWN1的處理.           
2.若ACTION_DOWN事件處理方式是3.mMotionTarget==B,           
2.1.1若A截取該事件mMotionTarget==null;return true。           
2.1.2若A不截取,return B.dispatchTarget.           
第三ACTION_UP事件:           
1.ACTION_MOVE同1,則同ACTION_DOWN1.           
2.ACTION_MOVE同2.1.1,則同ACTION_DOWN1           
3.ACTION_MOVE同2.1.2同ACTION_MOVE2           
是以當ACTION_DOWN被目标視圖正确的處理(dispatchTouchEvent傳回true),後續事件才有可能會傳遞到目标視圖,因為在中途可以被view tree截取掉,這樣子也是不會傳遞到目标視圖的.若目标視圖(在處理ACTION_DOWN)沒有傳回true或者在ACTION_DOWN的情形下截斷,則目前的mMotionTarget==null,則事件交由view的dispatcheTouchEvent處理。如果截斷的是其他事件則目前ViewGroup直接傳回true,事件一旦被截斷再也不能傳遞到子視圖(隻可能傳遞ACTION_CANCEL,詳情看代碼).