View事件分發(fā)
點擊事件的傳遞規(guī)則
點擊事件首先要分析的對象就是MotionEvent
式廷,即點擊事件,其實所謂的點擊事件的事件的华弓,其實就是對MotionEvent
事件的分發(fā)過程食零,即當(dāng)一個MotionEvent
產(chǎn)生之后,系統(tǒng)需要把這號事件傳遞給一個具體的View寂屏,而這個傳遞的過程就是View的事件分發(fā)過程贰谣。
其中,點擊事件的分發(fā)過程由三個方法共同完成:dispatchTouchEvent
迁霎,onInterceptTouchEvent
吱抚,onTouchEvent
。
以下要注意消耗和攔截的區(qū)別
dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event)
用來進(jìn)行事件的分開考廉,如果事件可以傳遞到當(dāng)前的View中秘豹,那么這個方法一定會被調(diào)用,返回結(jié)果受當(dāng)前View的onTouchEvent
和下級View的dispatchTouchEvent
的影響昌粤,表示是否消耗當(dāng)前事件既绕。
onInterceptTouchEvent
public boolean onInterceptTouchEvent(MotionEvent event)
在dispatchTouchEvent
方法內(nèi)部調(diào)用啄刹,用于判斷是否攔截某個事件,如果當(dāng)前View攔截了某個事件凄贩,那么在同一個事件序列中誓军,這個方法不會被再次調(diào)用,返回結(jié)果表示是否攔截當(dāng)前事件疲扎。
onTouchEvent
public boolean onTouchEvent(MotionEvent event)
在dispatchTouchEvent
方法內(nèi)部調(diào)用昵时,用來處理點擊事件,返回結(jié)果表示是否消耗當(dāng)前事件评肆,如果不消耗,則在同一個事件序列內(nèi)非区,當(dāng)前View無法再次接收到事件瓜挽。
以下是它們之間關(guān)系的偽代碼。
public boolean dispatchTouchEvent(MotionEvent event) {
boolean consume = false;
if (onInterceptTouchEvent(event)) {
consume = onTouchEvent(event);
} else {
consume = child.dispatchTouchEvent(event);
}
return consume;
}
上面的代碼可以大概了表現(xiàn)出事件的傳遞規(guī)則:對一個根ViewGroup來說征绸,點擊事件產(chǎn)生后久橙,首先會傳遞給它,這個時候它的dispatchTouchEvent
就會被調(diào)用管怠,如果ViewGroup的onInterceptTouchEvent
方法返回true淆衷,那么就表示它要攔截當(dāng)前事件,接著這個事件就會交給這個ViewGroup處理渤弛。即它的onTouchEvent
會被調(diào)用祝拯;而如果這個ViewGroup的onInterceptTouchEvent
方法返回false,那么就表示它不攔截當(dāng)前事件她肯,這個時候當(dāng)前事件就會傳遞給它的子元素佳头,接著子元素的dispatchTouchEvent
就會被調(diào)用,如此反復(fù)直至事件被最終處理晴氨。
onTouchListener與onClickListener
當(dāng)一個View需要處理事件時康嘉,如果它設(shè)置了OnTouchListener
,那么OnTouchListener
中的onTouch
方法會被回調(diào)籽前。這時候事件要根據(jù)onTouch
方法的返回值進(jìn)行處理亭珍,如果onTouch
返回true,那么onTouchEvent
方法將不會被調(diào)用枝哄;如果onTouch
返回false肄梨,那么onTouchEvent
將會被調(diào)用。由此可見挠锥,給View設(shè)置的OnTouchListener
峭范,優(yōu)先級比onTouchEvent
要高。而在onTouchEvent
方法中瘪贱,如果當(dāng)前設(shè)置的有onClickListener
,那么它的onClick
方法會被調(diào)用纱控,由此可見辆毡,onClickListener
優(yōu)先級最低。
點擊事件的傳遞順序
當(dāng)一個點擊事件產(chǎn)生之后甜害,它的傳遞過程遵循如下順序:Activity
->Window
->View
舶掖。事件總是先傳遞給Activity
,Activity
再傳遞給Window
尔店,最后Window
再傳遞給DecorView
眨攘。當(dāng)DecorView
接收到事件后,就會按照事件分發(fā)機制去處理嚣州。
考慮一種情況鲫售,如果一個View的onTouchEvent
返回了false,那么它的父容器的onTouchEvent
將會被調(diào)用该肴。以此類推情竹,假如所有的元素都不處理這個事件,那么這個事件最終會交給Activity處理匀哄。
事件傳遞的規(guī)則
對于事件傳遞的機制秦效,以下是其一些規(guī)則,根據(jù)規(guī)則可以更清楚地了解整個流程涎嚼。
- 同一個事件序列是指從手指從觸摸屏幕的那一刻起阱州,到手指離開手機的那一刻結(jié)束,在這個過程中產(chǎn)生的一系列事件法梯,這個事件以
down
事件開始苔货,中間含有數(shù)量不定的move
事件,最終以up
事件結(jié)束立哑。 - 正常情況下蒲赂,一個事件序列只能被一個View攔截并消耗,這條原因可以參照3刁憋,因為一旦一個元素攔截了某個事件滥嘴,那么同一個事件序列內(nèi)的所有事件都會直接交給它處理,因此同一個事件序列內(nèi)的事件由兩個View同時處理至耻,但是通過特殊手段可以做到若皱,比如一個View將本該直接自己處理的事件強行傳遞給其他View。
- 當(dāng)某個View一旦決定攔截尘颓,那么這一個事件序列都只能由它處理(如果事件序列能夠傳遞給它的話)走触,并且它的
onInterceptTouchEvent
不會被調(diào)用。也就是說疤苹,當(dāng)View決定攔截一個事件之后互广,系統(tǒng)會把同一個事件序列內(nèi)的其他方法都直接交給它處理,因此就不會再調(diào)用這個View的onInterceptTouchEvent
去詢問它是否攔截。 - 某個View一旦開始處理事件惫皱,如果它不消耗
ACTION_DOWN
事件像樊,即onTouchEvent
返回了false,那么同一事件序列的其他事件都不會交由它處理旅敷,并且事件將重新交由它的父元素去處理生棍,即父元素的onTouchEvent
會被調(diào)用。即事件一旦交給一個View處理媳谁,那么它必須消耗掉涂滴,否則同一事件的剩下的事件就不再交由它處理了。 - 如果View不消耗除
ACTION_DOWN
以外的事件晴音,那么這個點擊事件會消失柔纵,此時父元素的onTouchEvent
就不會被調(diào)用,并且當(dāng)前View可以持續(xù)收到后續(xù)的事件锤躁,最后這些消失的事件傳遞給Activity
處理搁料。 -
ViewGroup
默認(rèn)不攔截任何事件,Android源碼中的ViewGroup
的onInterceptTouchEvent
默認(rèn)返回false进苍。 -
View沒有
onInterceptTouchEvent
方法加缘,一旦有點擊事件傳遞給它鸭叙,那么它的onTouchEvent
將會被調(diào)用觉啊。 - View的
onTouchEvent
默認(rèn)都會消耗攔截事件(返回true),除非它是不可點擊的(即clickable
和longClickable
屬性同時為false)沈贝。View的longClickable
屬性默認(rèn)都為false杠人,View的clickable
屬性要區(qū)分情況,比如Button
的為true宋下,TextView
的為false嗡善。 - View的
enable
屬性不影響onTouchEvent
的默認(rèn)返回值。只要View的clickable
或者longClickable
有一個為true学歧,那么它的onTouchEvent
返回值就是true罩引。 -
onClick
發(fā)生的前提是當(dāng)前View是可點擊的,并且它收到了down
和up
事件枝笨。 - 事件的傳遞過程是由外向內(nèi)的袁铐,事件總是先傳遞給父元素,然后再由父元素傳遞給子View横浑,通過
requestDisallowInterceptTouchEvent
方法可以在子元素中干預(yù)父元素的分發(fā)過程剔桨,但是ACTION_DOWN
事件除外。