北京時間06月17日消息,中國觸摸屏網訊,用戶觸摸屏幕所產生的Touch Event在Android里是用一個MotionEvent對象來傳遞和處理的,我們最關注的是MotionEvent里的action,可以看到有ACTION_DOWN, ACTION_UP,ACTION_MOVE,ACTION_CANCEL,ACTION_POINTER_DOWN,ACTION_POINTER_UP等等很多種,在這里面最需要關注的是ACTION_DOWN和ACTION_UP,它們一個代表了用戶按壓動作的開始,一個代表了用戶按壓動作的結束,其他的一些ACTION基本都是發生在這2個ACTION之間的(ACTION_CANCEL等特殊的暫不討論)。
本文來自:http://www.zc28898.cn/touchscreen/news/front/201506/17-36507.html
在用戶的一次單手指觸摸屏幕過程中,簡單的講,會按順序產生一個ACTION_DOWN,若干個ACTION_MOVE,和一個ACTION_UP,我們下面的討論也會基于這個簡單case。
觸摸事件的傳遞處理順序
一個觸摸事件首先是在硬件層面觸發,然后逐層傳遞到軟件直至我們的app,前面的細節一般來說不用了解,我們討論的事件入口從Activity開始。
籠統的說(實際上細節有所不同,下面會提到)
觸摸事件的傳遞順序是:
Activity -> 當前活動窗口(PhoneWindow) -> 窗口的Top-Level View(DecorView) -> 各級ViewGroup (如各種Layout) -> ... -> 各級ViewGroup -> 葉子節點 View
而觸摸事件的處理順序則剛好相反:
葉子節點 View -> 各級ViewGroup -> ... -> 各級ViewGroup -> (Window和DecorView只有分發沒有處理) -> Activity
當葉子節點View接受到事件之后,會試圖做出處理,如果它處理了,則上面各層不再處理,如果它沒有處理則往上由它的父ViewGroup處理,這樣逐層向上按順序試圖處理,直到Activity。
分發和處理的關鍵函數
從上面籠統提到的事件分發處理順序可以看到,關鍵的分發處理集中在Activity,ViewGroup和View中,那么對于它們來說,有如下幾個分發和處理的關鍵函數,這里先做一個簡單介紹,后續再做詳細說明。
1,boolean dispatchTouchEvent(MotionEvent event)
這個函數是最關鍵的分發處理函數,里面既包含了分發的邏輯,又包含了對處理的邏輯調用
分發邏輯:這個函數會先調用子view的dispatchTouchEvent進行分發
處理邏輯:如果子view沒有對事件進行消化處理的話,這個函數就會調用本UI組件的處理函數如onTouchEvent
函數返回值表示這個觸摸事件是否被當前這個UI組件(Activity/ViewGroup/View)消化處理了
2,boolean onTouchEvent(MotionEvent event)
這個函數是UI組件自己實現用來響應處理觸摸事件的
函數返回值表示這個觸摸事件是否被當前這個UI組件(Activity/ViewGroup/View)消化處理了
3,View.OnTouchListener: boolean onTouch(View v, MotionEvent event)
這個函數不是Activity/ViewGroup/View本身的響應處理函數,而是一個Listener的響應處理函數
需要給View通過setOnTouchListener來設置Listener以使得這個onTouch函數能夠起作用
Activity沒有setOnTouchListener
函數返回值表示這個觸摸事件是否被當前這個UI組件(Activity/ViewGroup/View)消化處理了
4,ViewGroup: boolean onInterceptTouchEvent(MotionEvent event)
這個函數表明是否要攔截這個事件,前面提到過事件的分發順序是在View tress里從根到葉子逐層分發,處理則是反向的從葉子到根逐層處理,onInterceptTouchEvent如果返回true,則表示我這一層要攔截這個事件自己進行處理了,不要把它分發到子View里
這個函數只有ViewGroup含有,View沒有,因為View已經是葉子節點了,沒有子View了
這個函數默認是返回false的,一般不要輕易override它,因為常規來講是應該由子View先響應處理的
分發和處理的細節流程
ACTION_DOWN的處理
前面說過ACTION_DOWN是一個觸摸動作的起始,所以對ACTION_DOWN的處理和對其他事件的處理在細節上是有不同的,各個UI組件對于ACTION_DOWN事件的處理流程可以看到如下:
(注意這里 view 表示一個視圖組件,它可以是一個 View 也可以是一個 ViewGroup)
Activity -> dispatchTouchEvent:
通過getWindow().superDispatchTouchEvent(event)把事件分發到當前活動窗口(PhoneWindow),之后是 窗口的Top-Level View(DecorView),調用了DecorView的dispatchTouchEvent,DecorView繼承自ViewGroup,所以這里實際上就進入了ViewGroup層面的dispatchTouchEvent
如果superDispatchTouchEvent最終返回true(即下層的某個ViewGroup或者View消化處理了該函數,dispatchTouchEvent返回true),則直接返回
如果返回值為false,則調用Activity的onTouchEvent對事件進行處理
ViewGroup -> dispatchTouchEvent:
首先是檢查本view里是否保存了一個motion target(步驟4提到了怎么設置motion target),如果有則清除它
然后是調用onInterceptTouchEvent看這個事件是否需要被自己攔截,如果返回true,則直接進入步驟7開始自己處理事件的流程
如果返回false,則需要遍歷所有的子view,遍歷的順序是:
首先按照Z-order
在同一Z值下如果可以的話按照子view的drawing order,這里的drawing order需要ViewGroup的子類override了getChildDrawingOrder才會實際生效
遍歷子view的時候,如果這個觸摸事件發生的位置在這個view的視覺范圍以內,那么就調用child.dispatchTouchEvent將事件分發給這個子view,如果這個子view消化處理了這個事件(即dispatchTouchEvent返回true),本view會將該子view賦值給一個表明motion target的變量,且此時跳出循環
循環遍歷結束后,如果有子view處理了該事件(即motion target不為空),則返回true表明此事件已經被處理
如果沒有任何一個子view處理了該事件(即motion target不為空),則本view需要進行處理,進入步驟6
查看當前view是否注冊了OnTouchListener,如果有,則調用該listener的onTouch函數來處理事件,如果onTouch返回true表示消化處理了該事件,則直接返回true
如果onTouch返回false表示沒有處理,則繼續調用本view的onTouchEvent函數來處理事件,這里會返回onTouchEvent的返回值
View -> dispatchTouchEvent:
查看當前view是否注冊了OnTouchListener,如果有,則調用該listener的onTouch函數來處理事件,如果onTouch返回true表示消化處理了該事件,則直接返回true
如果onTouch返回false表示沒有處理,則繼續調用本view的onTouchEvent函數來處理事件,這里會返回onTouchEvent的返回值
其他ACTION的處理
上面說了ACTION_DOWN的處理,那ACTION_DOWN后續的ACTION_MOVE,ACTION_UP之類的事件又是怎么處理的呢?它們的處理方式略有不同:
在Activity層面來看,它們的處理和ACTION_DOWN沒有區別。
在ViewGroup層面來看,處理開始有了差異:
ViewGroup -> dispatchTouchEvent:
檢查當前view在之前處理ACTION_DOWN(ACTION_MOVE,ACTION_UP之類的事件一定是有一個配對的ACTION_DOWN事件在前面發生)的時候是否已經設置了一個motion target
如果有這個target,那么表明之前的ACTION_DOWN事件就是由該子view處理的,此時直接調用motion_target.dispatchTouchEvent
如果沒有這個target,那么表明之前的ACTION_DOWN沒有任何一個子view處理,那么后續這些事件也不要發給子view了,直接自己處理,進入步驟4
查看當前view是否注冊了OnTouchListener,如果有,則調用該listener的onTouch函數來處理事件,如果onTouch返回true表示消化處理了該事件,則直接返回true
如果onTouch返回false表示沒有處理,則繼續調用本view的onTouchEvent函數來處理事件,這里會返回onTouchEvent的返回值
注意整個流程中都跳過了onInterceptTouchEvent的攔截
在View層面來看,處理方式也是一樣的。
所以這里可以看到的現象就是:ACTION_DOWN被誰處理了,后續的ACTION_MOVE,ACTION_UP等事件最終也會交由誰處理。
onTouchEvent() or OnTouchListener.onTouch()?
從上面的內容可以看出來,我們要想對一個觸摸事件進行響應,可以在view的onTouchEvent()函數里處理,也可以給view設置一個OnTouchListener然后在listener的onTouch()函數里處理,那么它們有些什么區別?我們應該怎么選擇呢?
首先看區別:
onTouchEvent()是View自己的函數,對于我們來說無法override 各種View或者ViewGroup的onTouchEvent()函數,只能是在自己自定義的view里面實現override;而OnTouchListener的onTouch()是可以對任何View/ViewGroup起作用的,我們只需要在代碼里為該View/ViewGroup加一個listener就行
OnTouchListener的onTouch()的執行順序在view的onTouchEvent()之前,如果在onTouch()函數里面響應完了觸摸事件并返回true之后,onTouchEvent()是不會被調用的
知道了區別,那么我們就可以輕易的做出選擇了(只是一家之言,歡迎各種不同意見):
OnTouchListener的onTouch()基本上是萬能的,任何時候都可以用它,所以大部分時候直接用它就行了
在自定義view里面,如果出于代碼結構和功能清晰的目的,可以使用onTouchEvent()