Android Touch事件傳遞總結


Android Touch事件傳遞總結

字數1724 閱讀80 評論2

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實驗就夠了。


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