Android Touch事件傳遞總結
Android View的Touch事件傳遞主要涉及三個方法:
-
boolean dispatchTouchEvent(MotionEvent ev)
負責事件的分發,返回值的確定很複雜,涉及是否綁定OnTouchListener、onTouch方法返回值、onTouchEvent返回值,對於ViewGroup與onInterceptTouchEvent返回值有關。
-
boolean onInterceptTouchEvent(MotionEvent ev)
是否攔截事件。只有ViewGroup有該方法,因爲ViewGroup一般會包含子View,有時需要考慮對Touch事件的攔截。
-
boolean onTouchEvent(MotionEvent event)
消費Touch事件。返回值決定Touch事件是否向上傳遞給父ViewGroup。
看上面的描述肯定不懂,自己寫個例子試驗一下唄。
ViewGroupA和ViewGroupB繼承自ViewGroup,作爲兩個自定義的View容器。
紅色部分的View直接繼承自View類。
在這三個自定義類中重寫前面提到的相應方法,默認只打Log,同樣給Activity的dispatchTouchEvent和onTouchEvent打Log,在不同情形下來看看輸出。
一、默認情形
- 點擊一下紅色區域即ViewContent
- 默認都不攔截
- 默認都不消費
D/MainActivity: MainActivity dispatchTouchEvent ACTION_DOWN
D/ViewGroupA: ViewGroupA dispatchTouchEvent ACTION_DOWN
D/ViewGroupA: ViewGroupA onInterceptTouchEvent ACTION_DOWN
D/ViewGroupB: ViewGroupB dispatchTouchEvent ACTION_DOWN
D/ViewGroupB: ViewGroupB onInterceptTouchEvent ACTION_DOWN
D/ViewContent: ViewContent dispatchTouchEvent ACTION_DOWN
D/ViewContent: ViewContent onTouchEvent ACTION_DOWN
D/ViewGroupB: ViewGroupB onTouchEvent ACTION_DOWN
D/ViewGroupA: ViewGroupA onTouchEvent ACTION_DOWN
D/MainActivity: MainActivity onTouchEvent ACTION_DOWN
D/MainActivity: MainActivity dispatchTouchEvent ACTION_UP
D/MainActivity: MainActivity onTouchEvent ACTION_UP
調用順序出來了,一個完整的Touch事件流由DOWN事件開始,經歷0個或若干個MOVE事件(該例中未移動),最後UP事件結束。
事件的分發由上至下,事件到達某一層時先調用dispatchTouchEvent,由其決定將事件分發給哪個方法處理。
對於ViewGroup會傳遞給該層的onInterceptTouchEvent決定是否攔截,對於View則直接傳遞給onTouchEvent決定是否消費,該例中各層的onTouchEvent都返回默認false即都不消費事件,則事件會由子View的onTouchEvent向上傳遞至上層的onTouchEvent。
該例中的DOWN事件最終由MainActivity的onTouchEvent消費了,當後續的UP事件到來時,MainActivity的dispatchTouchEvent直接把UP事件交由其onTouchEvent處理。
示意圖如下:
二、某一層消費事件(onTouchEvent返回true)
- 讓ViewGroupB即藍色區域接收到DOWN事件後,onTouchEvent返回true,即ViewGroupB消費了DOWN事件
- onInterceptTouchEvent都不攔截事件
- 點擊紅色區域
D/MainActivity: MainActivity dispatchTouchEvent ACTION_DOWN
D/ViewGroupA: ViewGroupA dispatchTouchEvent ACTION_DOWN
D/ViewGroupA: ViewGroupA onInterceptTouchEvent ACTION_DOWN
D/ViewGroupB: ViewGroupB dispatchTouchEvent ACTION_DOWN
D/ViewGroupB: ViewGroupB onInterceptTouchEvent ACTION_DOWN
D/ViewContent: ViewContent dispatchTouchEvent ACTION_DOWN
D/ViewContent: ViewContent onTouchEvent ACTION_DOWN
D/ViewGroupB: ViewGroupB onTouchEvent ACTION_DOWN
D/MainActivity: MainActivity dispatchTouchEvent ACTION_UP
D/ViewGroupA: ViewGroupA dispatchTouchEvent ACTION_UP
D/ViewGroupA: ViewGroupA onInterceptTouchEvent ACTION_UP
D/ViewGroupB: ViewGroupB dispatchTouchEvent ACTION_UP
D/ViewGroupB: ViewGroupB onTouchEvent ACTION_UP
D/MainActivity: MainActivity onTouchEvent ACTION_UP
可以看出,DOWN事件從上至下分發,最下層的ViewContent沒有消費,向上傳遞時被ViewGroupB消費了,後續的UP事件會分發到ViewGroupB時直接交給它的onTouchEvent處理。
因爲DOWN事件被ViewGroupB消費了,後續事件也不用考慮攔截了(後續分發事件到ViewGroupB層時,ViewGroupB的onInterceptTouchEvent沒有調用),dispatchTouchEvent後直接給ViewGroupB的onTouchEvent處理。
示意圖:
三、某一層攔截事件(onInterceptTouchEvent返回true)
1. 接收到DOWN事件就攔截
- ViewGroupB的onInterceptTouchEvent在收到DOWN事件時返回true
- 都不消費事件,即onTouchEvent默認返回。
- 點擊紅色區域
D/MainActivity: MainActivity dispatchTouchEvent ACTION_DOWN
D/ViewGroupA: ViewGroupA dispatchTouchEvent ACTION_DOWN
D/ViewGroupA: ViewGroupA onInterceptTouchEvent ACTION_DOWN
D/ViewGroupB: ViewGroupB dispatchTouchEvent ACTION_DOWN
D/ViewGroupB: ViewGroupB onInterceptTouchEvent ACTION_DOWN
D/ViewGroupB: ViewGroupB onTouchEvent ACTION_DOWN
D/ViewGroupA: ViewGroupA onTouchEvent ACTION_DOWN
D/MainActivity: MainActivity onTouchEvent ACTION_DOWN
D/MainActivity: MainActivity dispatchTouchEvent ACTION_UP
D/MainActivity: MainActivity onTouchEvent ACTION_UP
可以看到,ViewGroupB的onInterceptTouchEvent在收到DOWN事件時返回true,即攔截了DOWN事件,那麼該事件直接交給它的onTouchEvent處理了,事件沒有下發到下層,若不消費事件則繼續向上找消費者。
2. DOWN事件不攔截,後續事件攔截
我們讓ViewGroupB接受DOWN事件時不攔截,即下層會收到DOWN事件,讓最底層View消費DOWN,在MOVE事件到來時ViewGroupB攔截,會發生什麼呢?
- ViewGroupB接受DOWN事件時不攔截
- 紅色區域的ViewContent收到DOWN的onTouchEvent返回true,即消費事件
- ViewGroupB接受MOVE事件時onInterceptTouchEvent返回true,即攔截MOVE事件
- 點擊紅色區域
D/MainActivity: MainActivity dispatchTouchEvent ACTION_DOWN
D/ViewGroupA: ViewGroupA dispatchTouchEvent ACTION_DOWN
D/ViewGroupA: ViewGroupA onInterceptTouchEvent ACTION_DOWN
D/ViewGroupB: ViewGroupB dispatchTouchEvent ACTION_DOWN
D/ViewGroupB: ViewGroupB onInterceptTouchEvent ACTION_DOWN
D/ViewContent: ViewContent dispatchTouchEvent ACTION_DOWN
D/ViewContent: ViewContent onTouchEvent ACTION_DOWN
D/MainActivity: MainActivity dispatchTouchEvent ACTION_MOVE
D/ViewGroupA: ViewGroupA dispatchTouchEvent ACTION_MOVE
D/ViewGroupA: ViewGroupA onInterceptTouchEvent ACTION_MOVE
D/ViewGroupB: ViewGroupB dispatchTouchEvent ACTION_MOVE
D/ViewGroupB: ViewGroupB onInterceptTouchEvent ACTION_MOVE
D/ViewContent: ViewContent dispatchTouchEvent ACTION_CANCEL
D/ViewContent: ViewContent onTouchEvent ACTION_CANCEL
D/MainActivity: MainActivity onTouchEvent ACTION_MOVE
D/MainActivity: MainActivity dispatchTouchEvent ACTION_MOVE
D/ViewGroupA: ViewGroupA dispatchTouchEvent ACTION_MOVE
D/ViewGroupA: ViewGroupA onInterceptTouchEvent ACTION_MOVE
D/ViewGroupB: ViewGroupB dispatchTouchEvent ACTION_MOVE
D/ViewGroupB: ViewGroupB onTouchEvent ACTION_MOVE
D/MainActivity: MainActivity onTouchEvent ACTION_MOVE
D/MainActivity: MainActivity dispatchTouchEvent ACTION_UP
D/ViewGroupA: ViewGroupA dispatchTouchEvent ACTION_UP
D/ViewGroupA: ViewGroupA onInterceptTouchEvent ACTION_UP
D/ViewGroupB: ViewGroupB dispatchTouchEvent ACTION_UP
D/ViewGroupB: ViewGroupB onTouchEvent ACTION_UP
D/MainActivity: MainActivity onTouchEvent ACTION_UP
看到了嗎,DOWN事件沒什麼問題,從各層分發直至找到它的消費者——ViewContent。但是,當後續的MOVE產生時,在分發過程中被ViewGroupB攔截,這時本來的消費者即下一層的ViewContent會收到CANCEL事件。
當MOVE再次產生時,直接分發到ViewGroupB層就停止了,會交給ViewGroupB的onTouchEvent處理MOVE,在不消費MOVE的情況下,MOVE事件都不會向上傳遞給ViewGroupA的onTouchEvent。
3. 子View請求父ViewGroup不攔截事件
在2的基礎上,DOWN被最底層消費,而MOVE和後續事件被上層攔截了。
如果在這種情形下,下層還想收到後續事件怎麼辦呢?可以調用:
getParent().requestDisallowInterceptTouchEvent(true);
這句代碼的作用是下層View請求父ViewGroup不攔截事件,在2的例子中最底層View消費DOWN後,調用這句,就可以繼續收到後續事件了。
D/MainActivity: MainActivity dispatchTouchEvent ACTION_DOWN
D/ViewGroupA: ViewGroupA dispatchTouchEvent ACTION_DOWN
D/ViewGroupA: ViewGroupA onInterceptTouchEvent ACTION_DOWN
D/ViewGroupB: ViewGroupB dispatchTouchEvent ACTION_DOWN
D/ViewGroupB: ViewGroupB onInterceptTouchEvent ACTION_DOWN
D/ViewContent: ViewContent dispatchTouchEvent ACTION_DOWN
D/ViewContent: ViewContent onTouchEvent ACTION_DOWN
D/MainActivity: MainActivity dispatchTouchEvent ACTION_MOVE
D/ViewGroupA: ViewGroupA dispatchTouchEvent ACTION_MOVE
D/ViewGroupB: ViewGroupB dispatchTouchEvent ACTION_MOVE
D/ViewContent: ViewContent dispatchTouchEvent ACTION_MOVE
D/ViewContent: ViewContent onTouchEvent ACTION_MOVE
D/MainActivity: MainActivity onTouchEvent ACTION_MOVE
注意1:在調用requestDisallowInterceptTouchEvent方法後,後續事件被分發時,各層都不走onInterceptTouchEvent方法。
注意2: 當某一層的View在調用了setOnClickListener或setOnTouchListener後,情況和注意1一樣,後續事件被分發時,各層都不走onInterceptTouchEvent方法。
注意3: 某一層在onTouchEvent消費DOWN事件,顯示返回true後,後續事件會嘗試繼續找它,對於無顯示返回true的後續事件,最終還會傳遞到MainActivity的onTouchEvent。
參考資料
關於Android Touch事件傳遞的文章和講解網上很多,其實有下面兩個+寫Demo實驗就夠了。
- 一個Dave Smith的講解視頻:Mastering the Android Touch System
- 上面視頻的課件:Mastering the Android Touch System.pdf