Android事件分發機制

jopen 8年前發布 | 16K 次閱讀 Android開發 移動開發

      在講之前先提出一個問題 :ListView的Item中有Button,onItemClick為什么會失效?
      在講Android時間分發機制之前,我們需要知道三個非常重要的方法:dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent,通過查看Android源碼可以看到,在View類中聲明了dispatchTouchEvent和onTouchEvent方法并沒有onInterceptTouchEvent。那onInterceptTouchEvent方法在哪里呢?通過查看View的子類ViewGroup的源碼可以看到,onInterceptTouchEvent是在ViewGroup中聲明,并默認返回了false。

// ViewGroup類中
public boolean onInterceptHoverEvent(MotionEvent event) {
        return false;
    }
      下面我們先來看看這三個方法的具體作用。

      public void dispatchTouchEvent(MotionEvent ev)

      用于事件的分發。如果事件傳遞給當前的View,那么這個方法一定會被調用,返回結果受當前View的onTouchEvent和下級View的dispatchTouchEvent方法影響。

      pubic void onInterceptTouchEvent(MotionEvent ev)

      是否攔截某個事件,返回false,不攔截事件,向子View進行分發(默認返回的是false)。返回true,則會中斷事件傳遞,并把事件交由當前View的onTouchEvent處理。

     public void onTouchEvent(MotionEvent ev)

      事件處理,返回結果表示是否消耗當前事件

      簡單分析了這三個方法之后,相信大部分童鞋還是有點不清楚這三個方法之間的關系和區別。這里就借用任大神的在他書中一段偽代碼來給講解

public boolean dispatchTouchEvent(MotionEvent ev){

        boolean consume  = false;
        // 判斷是否中斷當前事件
        if(onInterceptTouchEvent(ev)){
            // 如果onInterceptTouchEvent返回true 則調用當前View的onTouchEvent()方法
            consume = onTouchEvent(ev);
        }else{
            // 如果返回false 則調用子View的dispatchTouchEvent方法,向下分發事件
            consume  = childView.dispatchTouchEvent(ev);
        }

        return consume;
    }

通過這樣一段偽代碼,相信大家應該都初步理解的Android的事件傳遞機制。接下來我們接著向下分析。通過上面的分析我們得知,當一個事件發生之后,事件的傳遞順序如下:Activity接收到事件  ->Window  ->根Vew ->子view ->子View 。當Activity接收到事件,會傳遞給Window,而Window會傳遞個頂級View,然后根據上面我們分析的機制進行事件的分發傳遞,直到有View消耗此事件。如果當前View的onTouchEvent方法返回false,則上級View的onTouchEvent方法就會執行。依次類推,如果所有的View都沒有處理此事件,最終就會回到Activity手里,交由Activity進行處理。

      舉個簡單的例子,外包公司客戶想Boss提交了一個Bug,Boss一看這的改啊,但Boss肯定不會親自改,于是找來項目經理,“小李啊,來這有個Bug,去解決一下”,項目經理拿到Bug,經理嘛,怎能隨便出山。于是經理又叫來項目組長,“小曹,把這個bug搞定”。可項目組長此時正忙著開發新項目,沒時間。于是把bug有丟給新來的程序員小吳。小吳沒辦法,手下沒人了,心想尼瑪,欺負我新來的。于是找到組長,“老大,這玩意不會”,組長也懶得看bug,直接丟給經理,“不會”。經理一看,這要是不解決又要被老板批了,于是噼里啪啦把bug解決了。

   這里的Boss 就相當于Activity,而經理、項目組長相當于ViewGroup,而小吳相當于View ,Boss,經理,組長,小吳,都擁有解決問題的能力。這就相當于每一層都擁有事件處理的能力,而無論在哪一層進行了事件的處理,事件都會被消耗掉。如果Boss收到Bug之后,項目經理正好閑的蛋疼。把bug解決了,也就沒有組長小吳什么事了。但如果

大家都不想解決這個bug,最終一層拋一層。最終這個bug又會拋回到Boss手里。

    我們接著向下分析,這是View中的dispatchTouchEvent的源碼

/**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            // 從這里我們可以看到,如果為當前View設置了onTouchListener,則onTouch方法會優先于onTouchEvent方法執行
            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                return true;
            }

            if (onTouchEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }

從上面的源碼可以看到,如果我們設置了onTouchListener方法,onTouch是優先于onTouchEvent方法執行的,并且如果onTouch方法返回true時,onTouchEvent不會再執行。

       接下來我們再聊一聊onTouchEvent(MotionEvent ev)。當用戶點擊手機屏幕,到離開手機屏幕。這期間產生的一系列事件由一個down , 中間數量不定的若干個move 和一個up事件組成,這三種事件類型加起來,我們暫且稱他為一次事件集合。我們可以根據MotionEvent.getAction來判斷和處理各種事件類型。如果某個View一旦開始處理某個事件集合,就必須消耗掉Action_DOWN事件,也就是說在onTouchEvent必須返回true。否則后續事件不會在傳遞到當前View,事件會交由上級View的onTouchEvent進行處理。

      最后我們來總結一下

  •       當用戶點擊屏幕到手指離開屏幕,期間會產生一個Action_down 、數量不定的Action_move(可能是多個,也可能是沒有)、一個Action_up,我們稱之為一個事件集合,View決定處理一個事件集合是,必須消耗掉Action_down事件,后續事件不會傳遞到當前View,會交由上級View的onTouch處理;
  • ViewGroup默認不攔截任何事件,從上面的源碼也可以看出,Android源碼中的ViewGroup的onInterceptTouchEvent默認返回了false;
  • View 中沒有onInterceptTouchEvent方法,至于原因,就像上面的小吳,他沒有下級讓他分發事件。一旦有事件傳遞給它,那么它的onTouchEvent方法就會被調用。而其返回值受clickable和longclickable屬性影響,兩者有一個為true,則onTouchEvent默認返回true,二者同為false默認返回false,longclikable默認為false,click要分情況,TextView的click默認為false ,Button,checkBox默認屬性為true,這就是為什么在ListView的item中有Button按鈕,設置ListView的onItemClick會失效的原因所在。

到這里,Android的事件分發機制基本分析完畢,搞清楚Android的事件分發機制,對于我們事件監聽,以及自定義控件,都有非常大的幫助。


來自: http://blog.csdn.net/soul_code/article/details/50242487

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!