學習《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.總結
-
同一個事件序列是指從手指接觸屏幕的那一刻起,到手指離開屏幕的那一刻結束,在這過程中所產生的一系列事件,這個事件序列以down事件開始,中間含數量不定的move事件,最終以up事件結束。
-
正常情況下,一個事件序列只能被一個View攔截且消耗。因爲一旦一個元素攔截了此類事件,那麼同一個事件序列內的所以事件都會直接交給它處理,因此同一個事件序列中的事件不能分別由兩個View同時處理,但是通過特殊手段可以做到,比如一個View將本該自己處理的事件通過onTouchEvent強行傳遞給其他View處理。
-
某個View一決定攔截,那麼這個事件序列都會由它來處理(如果事件序列能夠傳遞給它的話),並且它的onIntercetTouchEvent不會再被調用。
就是說:當一個View決定攔截一個事件後,那麼系統會把同一個事件序列內的其他事件都直接交給它來處理,因此就不用再調用這個View的onIntercetTouchEvent方法去詢問是否要攔截了。
-
某個View一旦開始處理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent 返回了false),那麼同一事件序列中的其他事件都不會再交給它來處理,並且事件將重新交由它的父元素去處理,即父元素的onTouchEvent 會被調用。
意思是事件一旦交給一個View處理,那麼它必須消耗掉,否則同一事件序列中剩下的事件就不會再交給它來處理。就好比,領導有件事交給你去處理,如果你沒處理好,那麼短期內領導就不敢再把事情交給你處理了。
-
如果View不消耗ACTION_DOWN以外的其他事件,那麼這個點擊事件會消失,此時父元素的onTouchEvent 不會被調用,並且當前View可以持續收到後續的事件,最終這些消失的點擊事件將會傳遞給Activity處理。
-
ViewGroup默認不攔截任何事件。
-
View沒有onInterceptTouchEvent方法,一旦點擊事件傳遞給它,那麼它的onTouchEvent方法會被調用。
-
View的onTouchEvent方法默認都會消耗事件(返回true),除非它是不可點擊的(clickable和longClickable同時爲false)。View的longClickable屬性默認爲false,clickable屬性要分情況。如:Button爲true。TextView爲false。
-
View的enable屬性不影響onTouchEvent的默認返回值。就算View是disable狀態,只要clickable和longClickable有一個爲true,那麼它的onTouchEvent就返回true。
-
onClick會發生的前提是當前View是可點擊的,並且它收到了down和up的事件。
-
事件傳遞過程是由外向內的,即事件總是先傳遞給父元素,然後再由父元素分發給子View,在子View中可以通過requestDisallowInterceptTouchEvent方法干預父元素的事件分發過程,但是ACTION_DOWN除外。