版權聲明:本文為博主原創文章,未經博主允許不得轉載。
目錄(?)[+]
轉載請說明出處:http://blog.csdn.net/ff20081528/article/details/17353869
一、準備知識
1、視圖坐標與布局坐標的區別如下圖所示:
上圖是一個坐標系,這個坐標系是無邊無際的。這個無邊無際的坐標系即視圖坐標。手機屏幕可視范圍內的坐標即手機屏幕的布局坐標(坐標原點是屏幕的左上方的(0,0)位置)即A點。屏幕里面的子視圖里面可視范圍內的坐標即子視圖的布局坐標(坐標原點是子視圖的左上方的(0,0)位置)即B點。 2、android中布局關系 ?
二、例子說明事件分發過程
這里我寫了一個布局文件,展示效果如上圖。當我點擊View1,事件的分發過程是這樣的: 1、ViewGroup3的dispatchTouchEvent()方法會被調用。 2、ViewGroup3調用ViewGroup2的dispatchTouchEvent()方法。 3、ViewGroup2調用ViewGroup1的dispatchTouchEvent()方法。 4、ViewGroup1會調用View1的dispatchTouchEvent()方法。 5、View1的dispatchTouchEvent()方法調用自己的onTouchEvent()方法。在onTouchEvent方法中處理點擊事件。處理完了后會返回一個true給調用它的dispatchTouchEvent()方法。 6、ViewGroup1的dispatchTouchEvent()方法會返回一個true值給ViewGroup2的dispatchTouchEvent()方法。這樣一直將則個true值返回到ViewGroup3的dispatchTouchEvent()方法。ViewGroup3在將這個值返回給調用它的方法。這樣一個事件分發過程結束。 轉載請說明出處:http://blog.csdn.net/ff20081528/article/details/17353869 ? 三、ViewGroup中處理消息的詳細過程 通過上面的列子對事件分發的過程有個大概的了解之后,我們通過ViewGroup中的dispatchTouchEvent()方法源碼和View中的dispatchTouchEvent()方法及touchEvent方法源碼來詳細了解android是怎樣處理這個過程的。 ViewGroup中的dispatchTouchEvent()方法源碼如下:
[java] view plaincopy
@Override??????public?boolean?dispatchTouchEvent(MotionEvent?ev)?{??????????if?(!onFilterTouchEventForSecurity(ev))?{??????????????return?false;??????????}????????????final?int?action?=?ev.getAction();??????????final?float?xf?=?ev.getX();??????????final?float?yf?=?ev.getY();??????????final?float?scrolledXFloat?=?xf?+?mScrollX;??????????final?float?scrolledYFloat?=?yf?+?mScrollY;??????????final?Rect?frame?=?mTempRect;????????????boolean?disallowIntercept?=?(mGroupFlags?&?FLAG_DISALLOW_INTERCEPT)?!=?0;????????????if?(action?==?MotionEvent.ACTION_DOWN)?{??????????????if?(mMotionTarget?!=?null)?{??????????????????????????????????????????????????????????????????????????????????????????mMotionTarget?=?null;??????????????}??????????????????????????????????????????if?(disallowIntercept?||?!onInterceptTouchEvent(ev))?{????????????????????????????????????ev.setAction(MotionEvent.ACTION_DOWN);??????????????????????????????????????????????????????final?int?scrolledXInt?=?(int)?scrolledXFloat;??????????????????final?int?scrolledYInt?=?(int)?scrolledYFloat;??????????????????final?View[]?children?=?mChildren;??????????????????final?int?count?=?mChildrenCount;????????????????????for?(int?i?=?count?-?1;?i?>=?0;?i--)?{??????????????????????final?View?child?=?children[i];??????????????????????if?((child.mViewFlags?&?VISIBILITY_MASK)?==?VISIBLE??????????????????????????????||?child.getAnimation()?!=?null)?{??????????????????????????child.getHitRect(frame);??????????????????????????if?(frame.contains(scrolledXInt,?scrolledYInt))?{????????????????????????????????????????????????????????????final?float?xc?=?scrolledXFloat?-?child.mLeft;??????????????????????????????final?float?yc?=?scrolledYFloat?-?child.mTop;??????????????????????????????ev.setLocation(xc,?yc);??????????????????????????????child.mPrivateFlags?&=?~CANCEL_NEXT_UP_EVENT;??????????????????????????????if?(child.dispatchTouchEvent(ev))??{????????????????????????????????????????????????????????????????????mMotionTarget?=?child;??????????????????????????????????return?true;??????????????????????????????}????????????????????????????????????????????????????????????????????????????????????????????????????????????????????}??????????????????????}??????????????????}??????????????}??????????}????????????boolean?isUpOrCancel?=?(action?==?MotionEvent.ACTION_UP)?||??????????????????(action?==?MotionEvent.ACTION_CANCEL);????????????if?(isUpOrCancel)?{??????????????????????????????????????????mGroupFlags?&=?~FLAG_DISALLOW_INTERCEPT;??????????}????????????????????????????????final?View?target?=?mMotionTarget;??????????if?(target?==?null)?{??????????????????????????????????????????ev.setLocation(xf,?yf);??????????????if?((mPrivateFlags?&?CANCEL_NEXT_UP_EVENT)?!=?0)?{??????????????????ev.setAction(MotionEvent.ACTION_CANCEL);??????????????????mPrivateFlags?&=?~CANCEL_NEXT_UP_EVENT;??????????????}??????????????return?super.dispatchTouchEvent(ev);??????????}????????????????????????????????if?(!disallowIntercept?&&?onInterceptTouchEvent(ev))?{??????????????final?float?xc?=?scrolledXFloat?-?(float)?target.mLeft;??????????????final?float?yc?=?scrolledYFloat?-?(float)?target.mTop;??????????????mPrivateFlags?&=?~CANCEL_NEXT_UP_EVENT;??????????????ev.setAction(MotionEvent.ACTION_CANCEL);??????????????ev.setLocation(xc,?yc);??????????????if?(!target.dispatchTouchEvent(ev))?{??????????????????????????????????????????????????}????????????????????????????mMotionTarget?=?null;????????????????????????????????????????????????????????return?true;??????????}????????????if?(isUpOrCancel)?{??????????????mMotionTarget?=?null;??????????}????????????????????????????????final?float?xc?=?scrolledXFloat?-?(float)?target.mLeft;??????????final?float?yc?=?scrolledYFloat?-?(float)?target.mTop;??????????ev.setLocation(xc,?yc);????????????if?((target.mPrivateFlags?&?CANCEL_NEXT_UP_EVENT)?!=?0)?{??????????????ev.setAction(MotionEvent.ACTION_CANCEL);??????????????target.mPrivateFlags?&=?~CANCEL_NEXT_UP_EVENT;??????????????mMotionTarget?=?null;??????????}????????????return?target.dispatchTouchEvent(ev);??????}?? ?
代碼說明:
1、(代碼2-4行)處理窗口處于模糊顯示狀態下的消息。所謂的模糊顯示是指,應用程序可以設置當前窗口為模糊狀態,此時窗口內部的所有視圖將顯示為模糊效果。這樣的目的是為了隱藏窗口中的內容,對于其中的各個視圖而言,可以設置改視圖的FILTER_TOUCHERS_WHEN_OBSCURED標識,如存在該標識,則意味著用戶希望不要處理該消息。
2、(代碼6-11行)將ViewGroup的布局坐標轉換成視圖坐標。
3、(代碼16-58行)處理ACTION_DOWN消息,其作用是判斷該視圖坐標落到了哪個子視圖中。首先判斷該ViewGroup本身是否被禁止獲取TOUCH消息,如果沒有禁止,并且回調函數onInterceptTouchEvent中沒有消耗該消息,則開始尋找子視圖。調用child.getHitRect(frame)方法獲取該子視圖在父視圖中的布局坐標,即ViewGroup將child放在什么位置,這個位置相對于該child來講是布局坐標,而對于該ViewGroup來講卻是視圖坐標,參數frame是執行完畢后的位置輸出矩形。得到位置后,就可以調用frame.contain()方法判斷該消息位置是否被包含到了該child中,如果包含,并且該child也是一個ViewGroup,則準備遞歸調用該child的dispatchTouchEvent(),在調用之前,首先需要把坐標重新轉換到child的坐標系中。接下來判斷該child是否是ViewGroup類。如果是,則詆毀調用ViewGroup的dispatchTouchEvent(),重新從第一步開始執行;如果child不是ViewGroup,而是一個View,則意味著詆毀調用的結束。
4、(代碼61-68行)如果是ACTION_UP或者是ACTION_CANCEL消息,則清除mGroupFlags中的FLAG_DISALLOW_INTERCEPT標識,即允許該ViewGroup截獲消息。也就是說,常見的情況就是當用戶釋放手指,下一次按下時,該ViewGroup本身可以重新截獲消息,而在按下還沒有釋放期間,ViewGroup本身是不允許截獲消息的。 5、(代碼70-82)判斷target變量是否為空。空代表了所有子窗口都沒有消耗該消息,所以該ViewGroup本事需要處理該消息。然后還原消息的原始位置,將視圖坐標重新轉換成布局坐標,接著調用super.dispatchTouchEvent(ev),即View類的中的dispatchTouchEvent()方法(該方法會判斷是否進行調用touchEvent()方法來處理,到后面的View類的源碼中再介紹)。
6、(代碼84-121)處理target存在,并且變量disallowIntercept為false,即允許截獲,在默認情況下ViewGroup都是允許截獲消息的,只有當該ViewGroup的子視圖調用父視圖的requestDisallowdInterceptTouchEvent()方法時,方可禁止父視圖再次截獲消息,但每次ACTION_UP消息或者ACTION_CANCEL消息之后,該ViewGroup又會重新截獲消息。ViewGroup本身不允許截獲消息或者允許截獲但是卻沒有消耗消息,于是調用target.dispatchTouchEvent(ev)方法把該消息繼續交給目標視圖處理,在調用該方法前需要檢查target中是否申明過要取消隨后的消息,即mPrivateFlags中包含 CANCEL_NEXT_UP_EVENT,如果是,則把消息action值修改為ACTION_CANCEL,將mMotionTarget變為空,因為target不想處理接下來的消息了,那么就可以認為沒有target了。
?
四、View中處理消息的詳細過程
dispatchTouchEvent()方法的源碼
[java] view plaincopy
???????????public?boolean?dispatchTouchEvent(MotionEvent?event)?{?????????if?(!onFilterTouchEventForSecurity(event))?{?????????????return?false;?????????}???????????if?(mOnTouchListener?!=?null?&&?(mViewFlags?&?ENABLED_MASK)?==?ENABLED?&&?????????????????mOnTouchListener.onTouch(this,?event))?{?????????????return?true;?????????}?????????return?onTouchEvent(event);?????}?? 處理窗口處于模糊顯示狀態下的消息。然后回調視圖監聽著的onTouch()方法,如果監聽者消耗了該消息,則直接返回。如果沒有則調用View的onTouchEvent()方法。
?
onTouchEvent()方法源碼
[java] view plaincopy
????????????public?boolean?onTouchEvent(MotionEvent?event)?{??????????final?int?viewFlags?=?mViewFlags;????????????if?((viewFlags?&?ENABLED_MASK)?==?DISABLED)?{??????????????????????????????????????????return?(((viewFlags?&?CLICKABLE)?==?CLICKABLE?||??????????????????????(viewFlags?&?LONG_CLICKABLE)?==?LONG_CLICKABLE));??????????}????????????if?(mTouchDelegate?!=?null)?{??????????????if?(mTouchDelegate.onTouchEvent(event))?{??????????????????return?true;??????????????}??????????}????????????if?(((viewFlags?&?CLICKABLE)?==?CLICKABLE?||??????????????????(viewFlags?&?LONG_CLICKABLE)?==?LONG_CLICKABLE))?{??????????????switch?(event.getAction())?{??????????????????case?MotionEvent.ACTION_UP:??????????????????????boolean?prepressed?=?(mPrivateFlags?&?PREPRESSED)?!=?0;??????????????????????if?((mPrivateFlags?&?PRESSED)?!=?0?||?prepressed)?{??????????????????????????????????????????????????????????????????????????????boolean?focusTaken?=?false;??????????????????????????if?(isFocusable()?&&?isFocusableInTouchMode()?&&?!isFocused())?{??????????????????????????????focusTaken?=?requestFocus();??????????????????????????}????????????????????????????if?(!mHasPerformedLongPress)?{????????????????????????????????????????????????????????????removeLongPressCallback();??????????????????????????????????????????????????????????????if?(!focusTaken)?{????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????if?(mPerformClick?==?null)?{??????????????????????????????????????mPerformClick?=?new?PerformClick();??????????????????????????????????}??????????????????????????????????if?(!post(mPerformClick))?{??????????????????????????????????????performClick();??????????????????????????????????}??????????????????????????????}??????????????????????????}????????????????????????????if?(mUnsetPressedState?==?null)?{??????????????????????????????mUnsetPressedState?=?new?UnsetPressedState();??????????????????????????}????????????????????????????if?(prepressed)?{??????????????????????????????mPrivateFlags?|=?PRESSED;??????????????????????????????refreshDrawableState();??????????????????????????????postDelayed(mUnsetPressedState,??????????????????????????????????????ViewConfiguration.getPressedStateDuration());??????????????????????????}?else?if?(!post(mUnsetPressedState))?{????????????????????????????????????????????????????????????mUnsetPressedState.run();??????????????????????????}??????????????????????????removeTapCallback();??????????????????????}??????????????????????break;????????????????????case?MotionEvent.ACTION_DOWN:??????????????????????if?(mPendingCheckForTap?==?null)?{??????????????????????????mPendingCheckForTap?=?new?CheckForTap();??????????????????????}??????????????????????mPrivateFlags?|=?PREPRESSED;??????????????????????mHasPerformedLongPress?=?false;??????????????????????postDelayed(mPendingCheckForTap,?ViewConfiguration.getTapTimeout());??????????????????????break;????????????????????case?MotionEvent.ACTION_CANCEL:??????????????????????mPrivateFlags?&=?~PRESSED;??????????????????????refreshDrawableState();??????????????????????removeTapCallback();??????????????????????break;????????????????????case?MotionEvent.ACTION_MOVE:??????????????????????final?int?x?=?(int)?event.getX();??????????????????????final?int?y?=?(int)?event.getY();??????????????????????????????????????????????int?slop?=?mTouchSlop;??????????????????????if?((x?<?0?-?slop)?||?(x?>=?getWidth()?+?slop)?||??????????????????????????????(y?<?0?-?slop)?||?(y?>=?getHeight()?+?slop))?{????????????????????????????????????????????????????removeTapCallback();??????????????????????????if?((mPrivateFlags?&?PRESSED)?!=?0)?{????????????????????????????????????????????????????????????removeLongPressCallback();??????????????????????????????????????????????????????????????mPrivateFlags?&=?~PRESSED;??????????????????????????????refreshDrawableState();??????????????????????????}??????????????????????}??????????????????????break;??????????????}??????????????return?true;??????????}????????????return?false;??????}????<p>???</p>??
1、代碼10-15)處理該視圖是否為disable狀態,如果是,什么都不處理,返回true,即消耗該消息。
2、(代碼17-21)消息代理處理消息。所謂的消息代理是指,可以給某個View指定一個消息處理代理,當View收到消息時,首先將該消息派發給其代理進行處理。如果代理內部消耗了該消息,則View不需要在進行任何處理;如果代理沒有處理,則view繼續處理。在這里不用多管,不會影響事件處理的邏輯和結果。
3、(代碼22-102)判斷該視圖是否可以點擊,如果不可以點擊,則直接返回false,即不處理該消息。否則,真正開始執行觸摸消息的默認處理邏輯,該邏輯分別處理了ACTION_DOWN、ACTION_MOVE、ACTION_UP消息.
(26-70)處理ACTION_UP消息,判斷該UP消息是否發生在前面所講的哪一個監測時間段中,并據此進行不同的處理。
(71-78)在ACTION_DOWN消息中,給mPrivateFlags變量添加PRESSED標識,并將變量mHasPerformLongPress設置為false,然后啟動tap監測,即發送一個異步延遲消息。
對于觸摸消息而言,消息本身沒有repeat的屬性,一次觸摸只有一個ACTION_DOWN消息,按下來就是連續的ACTION_MOVE消息,并最終以處理ACTION_CANCEL消息.
(80-85)ACTION_CANCLE消息處理,這里只需清除PRESSED標識,刷新視圖狀態,然后再關閉tap監測即可。
(86-105)對ACTION_MOVE消息進行處理。具體邏輯包括,判斷是否移動到了視圖區域以外,如果是,則刪除tap或者longPress的監測,并清除mPrivateFlags中的PRESSED標識,然后調用refreshDrawableState()刷新視圖狀態,這將導致對背景狀態進行重繪。
?
?五、例子程序
通過代碼可能完全理解事件的分發過程有點難,下面通過一個例子程序來鞏固下事件分發的過程。
例子程序的UI如下:
布局如下:
[java] view plaincopy
<org.sunday.main.MyLinearLayout?xmlns:android="http://schemas.android.com/apk/res/android"??????xmlns:tools="http://schemas.android.com/tools"??????android:layout_width="match_parent"??????android:layout_height="match_parent"??????android:orientation="vertical"?>?????????????<org.sunday.main.MyLinearLayout??????????android:id="@+id/ll2"??????????android:layout_width="match_parent"??????????android:layout_height="100dp"??????????android:background="@drawable/ll_selector"??????????android:orientation="vertical"?>??????</org.sunday.main.MyLinearLayout>????????<org.sunday.main.MyLinearLayout??????????android:id="@+id/ll1"??????????android:layout_width="match_parent"??????????android:layout_marginTop="20dp"??????????android:layout_height="100dp"??????????android:background="@drawable/ll_selector"??????????android:orientation="vertical"?>????????????<org.sunday.main.MyLinearLayout??????????????android:layout_width="80dp"??????????????android:layout_height="50dp"??????????????android:layout_marginTop="20dp"??????????????android:clickable="true"??????????????android:background="@drawable/ll_selector2"??????????????android:orientation="vertical"?>??????????</org.sunday.main.MyLinearLayout>??????</org.sunday.main.MyLinearLayout>????????<org.sunday.main.MyLinearLayout??????????android:id="@+id/ll3"??????????android:layout_width="match_parent"??????????android:layout_height="100dp"??????????android:layout_marginTop="20dp"??????????android:background="@drawable/ll_selector"??????????android:orientation="vertical"?>????????????<org.sunday.main.MyButton??????????????android:layout_width="wrap_content"??????????????android:layout_height="60dp"??????????????android:layout_margin="20dp"??????????????android:background="@drawable/btn_selector"??????????????android:text="button1"?/>??????</org.sunday.main.MyLinearLayout>????????<org.sunday.main.MyLinearLayout??????????android:id="@+id/ll4"??????????android:layout_width="match_parent"??????????android:layout_height="100dp"??????????android:layout_marginTop="20dp"??????????android:background="@drawable/ll_selector"??????????android:orientation="vertical"?>????????????<org.sunday.main.MyButtonWithOutEvent??????????????android:layout_width="wrap_content"??????????????android:layout_height="60dp"??????????????android:layout_margin="20dp"??????????????android:background="@drawable/btn_selector"??????????????android:text="button2"?/>??????</org.sunday.main.MyLinearLayout>????</org.sunday.main.MyLinearLayout>??
MyLinearLayout.java
[java] view plaincopy
package?org.sunday.main;????import?android.content.Context;??import?android.util.AttributeSet;??import?android.util.Log;??import?android.view.MotionEvent;??import?android.widget.LinearLayout;????public?class?MyLinearLayout?extends?LinearLayout?{??????private?final?static?String?TAG?=?"MyLinearLayout";????????public?MyLinearLayout(Context?context)?{??????????super(context);??????}????????public?MyLinearLayout(Context?context,?AttributeSet?attrs)?{??????????super(context,?attrs);??????}????????????@Override??????public?boolean?dispatchTouchEvent(MotionEvent?ev)?{??????????Log.e(TAG,?"dispatchTouchEvent()----------------"?+?this.toString());??????????boolean?isTrue?=?super.dispatchTouchEvent(ev);??????????Log.e(TAG,?"dispatchTouchEvent()??isTrue?=?"?+?isTrue?+?"?---------------?;"+?this.toString());??????????return?isTrue;??????}????????????@Override??????public?boolean?onTouchEvent(MotionEvent?event)?{??????????Log.e(TAG,?"onTouchEvent()----------------"?+?this.toString());??????????boolean?isTrue?=?super.onTouchEvent(event);??????????Log.e(TAG,?"onTouchEvent()??isTrue?=?"?+?isTrue+?"-----------------??;"+?this.toString());??????????return?isTrue;??????}????????}??
MyButton.java
[java] view plaincopy
package?org.sunday.main;????import?android.content.Context;??import?android.util.AttributeSet;??import?android.util.Log;??import?android.view.MotionEvent;??import?android.widget.Button;????public?class?MyButton?extends?Button?{??????private?final?static?String?TAG?=?"MyButton";????????public?MyButton(Context?context)?{??????????super(context);??????}????????public?MyButton(Context?context,?AttributeSet?attrs)?{??????????super(context,?attrs);??????}????????@Override??????public?boolean?dispatchTouchEvent(MotionEvent?event)?{??????????Log.e(TAG,?"dispatchTouchEvent()----------------");??????????boolean?isTrue?=?super.dispatchTouchEvent(event);??????????Log.e(TAG,?"dispatchTouchEvent??isTrue?=?"?+?isTrue);??????????return?isTrue;??????}????????????@Override??????public?boolean?onTouchEvent(MotionEvent?event)?{??????????Log.e(TAG,?"onTouchEvent()----------------");??????????boolean?isTrue?=?super.onTouchEvent(event);??????????Log.e(TAG,?"onTouchEvent??isTrue?=?"?+?isTrue);??????????return?isTrue;??????}??}??
MyButtonWithOutEvent.java
[java] view plaincopy
package?org.sunday.main;????import?android.content.Context;??import?android.util.AttributeSet;??import?android.util.Log;??import?android.view.MotionEvent;??import?android.widget.Button;????public?class?MyButtonWithOutEvent?extends?Button?{??????private?final?static?String?TAG?=?"MyButtonWithOutEvent";????????public?MyButtonWithOutEvent(Context?context)?{??????????super(context);??????}????????public?MyButtonWithOutEvent(Context?context,?AttributeSet?attrs)?{??????????super(context,?attrs);??????}????????@Override??????public?boolean?dispatchTouchEvent(MotionEvent?event)?{??????????Log.e(TAG,?"dispatchTouchEvent()----------------"+?this.toString());??????????????return?false;??????}????????????@Override??????public?boolean?onTouchEvent(MotionEvent?event)?{??????????Log.e(TAG,?"onTouchEvent()----------------");??????????boolean?isTrue?=?super.onTouchEvent(event);??????????Log.e(TAG,?"onTouchEvent??isTrue?=?"?+?isTrue);??????????return?isTrue;??????}??}??
?點擊例子一,后臺打印的日志如下:
?
解釋:例子一的布局中只有兩個ViewGroup子類對象,當點擊例子一區域,根ViewGroup首先分發touch事件,因為它有child,所以接著會將事件傳遞到它的孩子ViewGroup。孩子ViewGroup發現自己沒有child了,所以它就得自己處理這個touch事件。所以會調用OnTouchEvent()方法來處理這個事件。處理完之后會返回一個true,一直返回給根ViewGroup。
?
點擊例子二的綠色區域,后臺打印日志如下:
解釋:例子二和例子一差不多,只不過多了一個ViewGroup而已。
?
點擊例子三得黃色區域,后臺打印日志如下:
?解釋:例子三相比于例子二只是將最上層的ViewGroup換成了View,原理一樣。
?
點擊例子四的黃色區域,打印日志如下:
解釋:在這個程序中,對于MyLinearLayout和MyButton的dispachTouchEvent()和onTouchEvent()發放的邏輯并沒有進行任何的修改。而對于MyButtonWithOutEvent,在dispatchTouchEvent()方法中,直接返回的false(即不進行事件的任何處理),那么事件傳遞到這里時,它將會把事件返回給調用它的父ViewGroup進行處理,所以這里我們看到的是MyLinearLayout調用了onTouchEvent()。
?
點擊例子三得button和點擊例子四的button效果圖如下:
?例子三中是button響應了這個touch事件,而在例子四中則是LinearLayout響應了這個touch事件。
demo:點擊打開鏈接
總結
以上是生活随笔為你收集整理的View工作原理(一)事件传递原理详解的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。