我覺得要想掌握android中View的事件分發機制,需要熟悉三個方法和一個優先級。
三個方法
- public boolean dispatchTouchEvent(MotionEvent ev)
用來事件的分發,只要事件能夠傳遞到當前View,那麼此方法一定會被調用。返回值表示是否消耗當前事件。
- public boolean onInterceptTouchEvent(MotionEvent ev)
在上述方法內部調用,用來判斷是否攔截某個事件。如果當前View攔截了某個事件,那麼在同一事件序列中,此方法不會再被調用(直接調用onTouchEvent方法),返回結果表示是否攔截當前事件。只有ViewGroup中有此方法,且默認返回false即ViewGroup默認不攔截任何事件。
- public boolean onTouchEvent(MotionEvent event)
在dispatchTouchEvent方法中調用,用來處理點擊事件,返回結果表示是否消耗當前事件,如果不消耗,那麼在同一個事件序列中,當前View無法再次接收到事件。
注:同一個事件序列指的是當前事件的ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCLE等一系列完整的事件。
優先級
這裏的優先級說的是onTouchListener.onTouch、onTouchEvent、onClick等方法執行的優先級。
先看一段源碼
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
可以看出當設置了onTouchListener時,會先執行onTouchListener的onTouch方法,如果該方法返回false,那麼回接着執行onTouchEvent方法,如果返回true,則不會執行onTouchEvent方法。所以可以得出結論:onTouchListener的優先級大於onTouchEvent方法。那麼onClick方法何時執行呢?接着看源碼:
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
這個方法是在onTouchEvent方法中對ACTION_UP事件的處理時調用的,由此我們可以看出在onTouchEvent方法中,且設置了onClickListener,onClick方法纔會被調用。即:onClick方法的優先級是最低的。
一般性結論
- 同一個事件序列是指從手指接觸屏幕那一刻起,到手指離開屏幕那一刻結束,在這個過程中所產生的一系事件,這個事件序列以ACTION_DOWN事件開始,中間含有數量不定的ACTION_MOVE事件,最終以ACTION_UP事件結束。
- 一個事件序列只能被一個View攔截且消耗,因爲一旦一個View攔截了某事件,那麼同一個時間序列的所有事件都會交給它處理。
- 某個View一旦決定攔截事件,那麼這一個事件序列都只能由它處理。
- 某個View一旦開始處理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent方法返回false),那麼同一事件序列的其他方法都不會在交給它處理,並且事件將重新交給父控件去處理,即父控件的onTouchEvent方法被調用。
- 如果View不消耗ACTION_DOWN以外的其他事件,那麼這個點擊事件會消失,此時父控件的onTouchEvent方法並不會調用,並且當前View可以持續接收到後續事件,最終這些消失的點擊事件會交給Activity處理。
- ViewGroup默認不攔截任何事件。
- View中沒有onInterceptTouchEvent方法,一旦有點擊事件傳遞給它,那麼它的onTouchEvent方法就會被調用。
- View的onTouchEvent默認都會消耗事件(即返回true),除非它是不可點擊的(clickable和longClickable同時爲false),View的longClickable屬性默認都爲false,clickable屬性要分情況,比如Button的clickable屬性默認爲true,而TextView的clickable屬性默認爲false。
- View的enable屬性不影響onTouchEvent的默認返回值。哪怕一個View是disable狀態的,只要它的clickable或者longClickable屬性有一個爲true,那麼它的onTouchEvent就返回true。
實例驗證
說了這麼多結論,下面我們用一個例子驗證一下。
爲此,我自定義了四個View,分別是MyLineatLayout、MyRelativeLayout、MyTextView、MyButton,其實現都很簡單,只是重寫了dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent方法,並在方法中打印了方法名和返回值。這四個View的佈局關係如下所示:
<com.wecanstudio.xdsjs.myviewdemo.MyLinearLayout
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_alignParentLeft="true"
android:layout_centerInParent="true"
android:background="@android:color/holo_red_light">
<com.wecanstudio.xdsjs.myviewdemo.MyRelativeLayout
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center"
android:background="@android:color/holo_green_dark">
<com.wecanstudio.xdsjs.myviewdemo.MyTextView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_alignParentLeft="true"
android:layout_centerInParent="true"
android:background="@android:color/holo_blue_bright"
android:gravity="center"
android:text="This is a TextView" />
<com.wecanstudio.xdsjs.myviewdemo.MyButton
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_alignParentRight="true"
android:layout_centerInParent="true"
android:background="@android:color/darker_gray"
android:gravity="center"
android:text="This is a Button" />
</com.wecanstudio.xdsjs.myviewdemo.MyRelativeLayout>
</com.wecanstudio.xdsjs.myviewdemo.MyLinearLayout>
當我點擊MyTextView時,打印日誌如下:
可以看到先是調用了MainActivity的dispatchTouchEvent,然後又接着調用了MyLinearLayout和MyRelativeLayout的dispatchTouchEvent和onInterceptTouchEvent方法,此時應該注意到這個View都沒有攔截該事件,即onInterceptTouchEvent返回的都是false,這也驗證了我們上面的結論,即ViewGroup默認是不攔截事件的。
最終這一事件傳遞到了MyTextView,MyTextView作爲一個普通的View,當有事件到達時肯定會執行onTouchEvent方法的,但是TextView的clickable和longClickable屬性默認都是false的,所以此時onTouchEvent方法返回的false,即不消耗當前事件。按照我們上面的結論,如果當前View不消耗當前事件的話,那麼就會返回給父控件的onTouchEvent,所以我們看到接着就打印出了MyLinearLayout、MyRelativeLayout、MainActivity的onTouchEvent方法。這也驗證了我們上面得出的結論。
到此時,這一事件序列中的ACTION_DOWN事件已經處理完畢。然後ACTION_UP事件觸發,可以看到此時MainActivity沒有向下分發這一事件,而是直接自己處理了。這其實同樣驗證了我們上面的結論,即某個View一旦開始處理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent方法返回false),那麼同一事件序列的其他方法都不會在交給它處理。
當我點擊MyButton時打印日誌如下:
可以看到,大致過程和上面是一樣的,不過由於Button是可點擊的(clickable爲true),所以當事件傳遞到MyButton時,其消耗了這一事件(onTouchEvent放回true)。所以這一事件序列的其他事件也都交由它處理。
關於此博客的演示代碼已上傳,歡迎下載。
http://download.csdn.net/detail/u012483425/9258795