講一講Android中View的事件分發機制

學習《Android開發藝術探索》-View事件分發機制總結

點擊事件的分發過程由三個很重要的方法來共同完成:dispatchTouchEvent、onInterceptTouchEven和onTouchEvent;

* ViewGroup的相關事件有三個:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。
* View的相關事件只有兩個:dispatchTouchEvent、onTouchEvent。

1.dispatchTouchEvent 方法

public boolean dispatchTouchEvent(MotionEvent ev)

用於進行事件的分發。如果事件能夠傳遞給當前View,那麼此方法一定會被調用,返回結果受到當前View的onTouchEvent和下級View的dispatchTouchEvent方法影響,表示是否消耗當前事件。

  • return true,事件會分發給當前 View 並由 dispatchTouchEvent 方法進行消費,同時事件會停止向下傳遞;
  • return false,事件分發分爲兩種情況:
    • 1.如果當前 View 獲取的事件直接來自 Activity,則會將事件返回給 Activity 的 onTouchEvent 進行消費;
    • 2.如果當前 View 獲取的事件來自外層父控件,則會將事件返回給父 View 的 onTouchEvent 進行消費。
  • return 系統默認的 super.dispatchTouchEvent(ev),事件會自動的分發給當前 View 的 onInterceptTouchEvent 方法。

2.onInterceptTouchEven 方法

public boolean onInterceptTouchEvent(MotionEvent ev) 

用來判斷是否攔截某個事件,如果當前View攔截了某個事件,那麼在同一事件序列中,此方法不會再次被調用。返回結果表示是否攔截當前事件。

  • return true,則表示將事件進行攔截,並將攔截到的事件交由當前 View 的 onTouchEvent 進行處理;
  • return false,則表示將事件放行,當前 View 上的事件會被傳遞到子 View 上,再由子 View 的 dispatchTouchEvent 來開始這個事件的分發;
  • return super.onInterceptTouchEvent(ev),ViewGroup裏的onInterceptTouchEvent默認返回值是false,這樣touch事件會傳遞到View控件;

3.onTouchEvent 方法

public boolean onTouchEvent(MotionEvent ev)

同來處理點擊事件,返回結果表示是否消耗當前事件,如果不消耗,則在同一事件序列中,當前View無法再次接收到事件。

  • 如果事件傳遞到當前 View 的 onTouchEvent 方法,而該方法返回了 false,那麼這個事件會從當前 View 向上傳遞,並且都是由上層 View 的 onTouchEvent 來接收,如果傳遞到上面的 onTouchEvent 也返回 false,這個事件就會“消失”,而且接收不到下一次事件。
  • 如果返回了 true 則會接收並消費該事件。
  • 如果返回 super.onTouchEvent(ev) ViewGroup裏的onTouchEvent默認返回值是false ,View裏的onTouchEvent默認返回值是true。

4.三者關係可以如下僞代碼表示:(是僞代碼、僞代碼、僞代碼)

public boolean dispatchTouchEvent(MotionEvent event){
    boolean consume= false;
    if(onInterceptTouchEvent(ev)){   
		consume = onTouchEvent(ev); 
	}else{    
		consume= childView.dispatchTouchEvent(ev);   
	}
    return result;
}

5.View(不包含ViewGroup)的點擊事件處理過程:

	當一個View需要處理事件時,要先判斷它是否設置了OnTouchListener。因爲如果它設置了OnTouchListener,那麼OnTouchListener中的onTouch方法會被調用。
	這時候事件如何處理要看onTouch的返回值
	  - 如果返回 false ,那麼當前View的onTouchEvent方法會被調用。
	  - 如果返回 true,那麼View的onTouchEvent方法將不會被調用。

View中優先級:OnTouchListener>onTouchEvent >OnClickListener。

當一個事件產生後,他的傳遞過程遵循如下順序:Actovity->Window->View,即事件總是先傳遞給Activity,Activity在傳遞給Window,最後Window傳遞給頂級View。頂級View接收到事件後,就會按照事件分發機制去分發事件。

如果一個View的onTouchEvennt返回false,那麼它的父容器的onTouchEvennt將會被調用,依此類推。當所有的元素都不處理該事件,那麼這個事件將會最終傳遞給Activity處理,即Activity的onTouchEvennt會被調用。

子View可通過requestDisallowInterceptTouchEvent方法干預父View的事件分發過程,但是ACTION_DOWN是除外;
如在Listview中解決ScrollView滑動衝突問題,可在Listview中加入如下代碼:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) { 
    //通知父層ViewGroup不截獲  
    getParent().requestDisallowInterceptTouchEvent(true);  
    return super.dispatchTouchEvent(ev);    
} 
//或者
 mListView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
            //當用戶滑動的時候,我們告訴父組件,不要攔截我的事件(這個時候子組件是可以正常響應事件的),拿起之後就會告訴父組件可以阻止。
            //ACTION_DOWN 的時候requestDisallowInterceptTouchEvent會被重置
                switch (event.getAction()) {
                    case MotionEvent.ACTION_MOVE:                     
                        v.getParent().requestDisallowInterceptTouchEvent(true);
                        break;
                    case MotionEvent.ACTION_UP:
                    case MotionEvent.ACTION_CANCEL:  
                         v.getParent().requestDisallowInterceptTouchEvent(false);
                        break;
                }
                return false;
            }
        }); 
 }

6.總結

  1. 同一個事件序列是指從手指接觸屏幕的那一刻起,到手指離開屏幕的那一刻結束,在這過程中所產生的一系列事件,這個事件序列以down事件開始,中間含數量不定的move事件,最終以up事件結束。

  2. 正常情況下,一個事件序列只能被一個View攔截且消耗。因爲一旦一個元素攔截了此類事件,那麼同一個事件序列內的所以事件都會直接交給它處理,因此同一個事件序列中的事件不能分別由兩個View同時處理,但是通過特殊手段可以做到,比如一個View將本該自己處理的事件通過onTouchEvent強行傳遞給其他View處理。

  3. 某個View一決定攔截,那麼這個事件序列都會由它來處理(如果事件序列能夠傳遞給它的話),並且它的onIntercetTouchEvent不會再被調用。

    就是說:當一個View決定攔截一個事件後,那麼系統會把同一個事件序列內的其他事件都直接交給它來處理,因此就不用再調用這個View的onIntercetTouchEvent方法去詢問是否要攔截了。 
    
  4. 某個View一旦開始處理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent 返回了false),那麼同一事件序列中的其他事件都不會再交給它來處理,並且事件將重新交由它的父元素去處理,即父元素的onTouchEvent 會被調用。

    意思是事件一旦交給一個View處理,那麼它必須消耗掉,否則同一事件序列中剩下的事件就不會再交給它來處理。就好比,領導有件事交給你去處理,如果你沒處理好,那麼短期內領導就不敢再把事情交給你處理了。
    
  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爲true。TextView爲false。

  9. View的enable屬性不影響onTouchEvent的默認返回值。就算View是disable狀態,只要clickable和longClickable有一個爲true,那麼它的onTouchEvent就返回true。

  10. onClick會發生的前提是當前View是可點擊的,並且它收到了down和up的事件。

  11. 事件傳遞過程是由外向內的,即事件總是先傳遞給父元素,然後再由父元素分發給子View,在子View中可以通過requestDisallowInterceptTouchEvent方法干預父元素的事件分發過程,但是ACTION_DOWN除外。

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