本篇部落格描述我閱讀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,詳情看代碼).