標籤:
一.不知道你是否在涉及到Android觸屏事件的時候有過如下的疑問:
1.View的onTouchEvent()方法返回true和false有什麼區別? SDK給出的解釋很簡單:"返回true代表該事件已經被處理過了,返回false則相反",這句話完全沒有解釋清楚問題。
2.View的onTouchEvent()方法在處理ACTION_DOWN的時候返回true,在處理ACTION_MOVE的時候返回false,代表著是處理了還是沒處理?返回super.onTouchEvent()又是什麼含義?
3.重寫onTouchEvent()方法和通過setOnTouchListener()設定一個觸屏監聽有什麼區別,看起來好像很類似。
4.View的dispatchTouchEvent(),onTouchEvent(),setOnClickListener(),ViewGroup的onInterceptTouchEvent()把我繞暈了,這些方法怎麼使用怎麼重寫?
5.假設一個ViewGroup有兩個子view,這兩個view有一部分是重疊的,點擊該重疊部分,事件由哪個View來處理?
6.最重要的一點疑問是:觸屏事件從頂層ViewGroup一直向下是怎麼傳遞的?
如果你有類似的疑問,相信我的這篇部落格能給你答案。
二.首先需要明確的幾點是:
1.View一般是為了顯示某些內容而存在的,它也通常用來處理使用者的觸屏等互動事件,而ViewGroup則是做為View的容器而存在的,雖然在代碼上它是View的子類,但它通常只是做為容器用來組織它的子視圖布局方式。
2.我們知道android裡邊View層次是一種樹型結構,需要明確的是一個ViewGroup它的直接子視圖才算是樹結構中的兒子,再往下一層就不算了,類似於進程間的父子關係。舉例,FrameLayout有兩個子視圖,分別是LinearLayout和TextView,而 LinearLayout又有三個子視圖ImageView,那麼調用FrameLayout的getChildCount()方法只會返回2,而不是 5。因此以下內容中"子視圖"這個術語代表著一個ViewGroup的直接子視圖,它即可能是一個View類,也可能是一個ViewGroup類。
3.Activity視圖的最頂層View是DecorView,它是在PhoneWindow類中通過generateDecor()方法產生的,它繼承自FrameLayout,是View層次的根視圖。
4.對於觸屏來說有三個主要的事件:down,move,up
那麼一個觸屏事件到底是怎麼在View層次中上向下傳遞的?(這裡只考慮事件已經到達DecorView時的情形,事實上是ViewRootImpl 類接收到底層InputDispatch傳遞過來的事件,這裡就不寫了),ViewRootImpl在deliverPointerEvent()方法中通過調用mView.dispatchPointerEvent(event);將觸屏事件傳遞給了DecorView,DecorView通過 dispatchTouchEvent()繼續向下傳遞給子視圖,如果子視圖也是一個ViewGroup,它又會調用自己的 dispatchTouchEvent()方法向下傳遞,如果子視圖是一個View,那麼子視圖的onTouchEvent()方法就會被調用,如果子視圖處理了該事件,那麼事件傳遞就中止。整個過程像是一個遞迴過程,理解了一個ViewGroup怎麼通過dispatchTouchEvent()傳遞給它的子視圖這一層也就理解了整個過程。
這裡就不分析ViewGroup的dispatchTouchEvent()方法的代碼了,直接給出我總結出來的結論,有興趣的讀者可以分析看看。
三.總結
以下情景假設一個ViewGroup有三個子視圖,按index順序為v1,v2,v3。v1也是一個ViewGroup,v2和v3都是普通的view,而且它們有一點重疊的部分。
1.ViewGroup的dispatchTouchEvent()向下分發事件給它的子視圖,那麼會先分發給v3調用它的onTouchEvent 方法,如果v3不處理該事件,會繼續分發給v2,如果v2不處理事件,會繼續分發給v1,由於v1是一個ViewGroup,則會調用它的 dispatchTouchEvent()分發給它的子視圖。
2.v3不處理該事件的含義是:在down事件到達時,onTouchEvent()方法返回false,如果在接收到down事件時返回true, 則表示處理了該事件,那麼不管你在接收到move和up事件的時候返回的是什麼都沒有關係。因此思想是:只要你願意處理down事件,那麼你必須處理接下來的其他事件。
3.v3能接收觸屏事件的前提是它的顯示矩形框必須在觸屏的範圍之內,這裡顯而易見的道理,否則事件會傳遞給v2。
4.如果v1,v2,v3都決定不處理觸屏事件,那麼事件最終由ViewGroup自己來處理,它的onTouchEvent()方法會被調用。
5.如果事件傳遞到了v1,v1是否處理取決於它的子視圖,如果它的子視圖有一個處理了該事件,那麼就代表v1處理了事件,如果它的所有子視圖都沒有處理事件而且v1本身的onTouchEvent()的方法在處理down事件的時候返回false,那麼才代表v1沒有處理事件。
6.如果通過setOnTouchListener()設定了一個有效監聽到view中,那麼事件到達時會直接調用這個監聽方法而不會調用onTouchEvent(),並返回true,表示已經處理了該事件。
到這裡,onTouchEvent()傳回值的含義應該很明確了,那麼super.onTouchEvent()傳回值是什麼呢?看代碼也比較簡單,如果一個view不是clickable的或者不是longClickable的,那麼super.onTouchEvent()直接返回false,否則就進行onClick和onLongClick處理並返回true。可調用 setClickable(true),setLongClickable(true)來改變view的狀態,調用 setOnClickListener()和setOnLongClickListener()也是一樣的效果。
由上面結論,如果兩視圖是父弟關係,它們又互有重疊部分,點擊該重疊部分,先處理該事件的是下標比較大的那個視圖,如果這個視圖不想處理事件,才讓另外一個處理。
四.onInterceptTouchEvent()
ViewGroup可以調用它的onInterceptTouchEvent()方法去攔截子視圖的事件,這個方法預設返回的是false表示不攔截,如果在onInterceptTouchEvent()接收到down事件時返回了true,那麼接下來的down,move和up事件都會被 ViewGroup自己的onTouchEvent()方法所接收,所有的子視圖都接收不到事件,而ViewGroup自己的 onInterceptTouchEvent()方法也只有down事件會被傳遞過去,因為都由父View來處理,所以該方法再接收到move和up事件就沒有意義了,所以只會有down事件會被傳遞。注釋中的說法:There are no touch targets and this action is not an initial down,so this view group continues to intercept touches.
如果一個子視圖決定處理全部三個事件,那麼每次事件到來時都會先調用父view的onInterceptTouchEvent()方法,如果在某個事件上返回了true,那麼就會攔截到該事件以及隨後的事件到ViewGroup自己的onTouchEvent()方法處理,子視圖會接收到 ACTION_CANCEL事件。
舉例:如果子視圖處理了down事件,但是ViewGroup在move到來時攔截住了move事件,那麼子視圖就收不到接下來的move和up事件,會收到ACTION_CANCEL事件,而ViewGroup則會接收move和up事件,onInterceptTouchEvent()方法也只會接收到down和move事件。
攔截方法有時非常有用,例如ScrollView它會先把down事件交給子視圖處理,如果是點擊事件,就交給子視圖,如果判斷出來正在拖動子視圖,那麼會攔截住move事件,交由自己處理,調用overScrollBy()產生滾動。
當然如果自己重寫了ViewGroup的dispatchTouchEvent()方法就自己掌控了事件的分發過程,和上面的流程就不一定一樣了。
總結完了,相信開頭的所有問題都有了答案,有點繞人,理清了就明白了。
轉載自:http://www.bdqn.cn/news/201312/12158.shtml
Android Touch事件的分發過程