Android中View的事件分發機制

我覺得要想掌握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方法的優先級是最低的

一般性結論

  1. 同一個事件序列是指從手指接觸屏幕那一刻起,到手指離開屏幕那一刻結束,在這個過程中所產生的一系事件,這個事件序列以ACTION_DOWN事件開始,中間含有數量不定的ACTION_MOVE事件,最終以ACTION_UP事件結束。
  2. 一個事件序列只能被一個View攔截且消耗,因爲一旦一個View攔截了某事件,那麼同一個時間序列的所有事件都會交給它處理。
  3. 某個View一旦決定攔截事件,那麼這一個事件序列都只能由它處理。
  4. 某個View一旦開始處理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent方法返回false),那麼同一事件序列的其他方法都不會在交給它處理,並且事件將重新交給父控件去處理,即父控件的onTouchEvent方法被調用。
  5. 如果View不消耗ACTION_DOWN以外的其他事件,那麼這個點擊事件會消失,此時父控件的onTouchEvent方法並不會調用,並且當前View可以持續接收到後續事件,最終這些消失的點擊事件會交給Activity處理。
  6. ViewGroup默認不攔截任何事件。
  7. View中沒有onInterceptTouchEvent方法,一旦有點擊事件傳遞給它,那麼它的onTouchEvent方法就會被調用。
  8. View的onTouchEvent默認都會消耗事件(即返回true),除非它是不可點擊的(clickable和longClickable同時爲false),View的longClickable屬性默認都爲false,clickable屬性要分情況,比如Button的clickable屬性默認爲true,而TextView的clickable屬性默認爲false。
  9. 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章