Android Touch事件傳遞機制解析
android系統中的每個ViewGroup的子類都具有下面三個和TouchEvent處理密切相關的方法:
1)public boolean dispatchTouchEvent(MotionEvent ev) 這個方法用來分發TouchEvent
2)public boolean onInterceptTouchEvent(MotionEvent ev) 這個方法用來攔截TouchEvent
3)public boolean onTouchEvent(MotionEvent ev) 這個方法用來處理TouchEvent
注意:不是所有的View的子類,很多教程都說的是所有的View的子類,只有可以向裡面添加View的控制項才需要分發,比如TextView它本身就是最小的view了,所以不用再向它的子視圖分發了,它也沒有子視圖了,所以它沒有dispatch和Intercept,只有touchEvent。
說明: 白色為最外層,它佔滿整個螢幕;
紅色為中間地區,屬於白色中的一層;
黑色為中心地區,必於紅色中的一層。
注意:他們本質上是:LinearLayout,而不是RelativeLayout或者其它布局。
1.由中心地區處理touch事件
布局檔案如下:
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical">
- android:id="@+id/view_out"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:background="#fff"
- android:gravity="center">
- android:id="@+id/view_mid"
- android:layout_width="300px"
- android:layout_height="400px"
- android:background="#f00"
- android:gravity="center">
- android:id="@+id/view_center"
- android:layout_width="150px"
- android:layout_height="150px"
- android:background="#000"
- android:gravity="center"
- android:clickable="true">
-
-
-
複製代碼
注意: android:clickable="true"
接下來我們看一下列印的日誌:
結合是上面的日誌,我們可以看一下ACTION_DOWN事件處理流程:
說明:
首先觸摸事件發生時(ACTION_DOWN),由系統調用Activity的dispatchTouchEvent方法,分發該事件。根據觸摸事件的座標,將此事件傳遞給out的dispatchTouchEvent處理,out則調用onInterceptTouchEvent 判斷事件是由自己處理,還是繼續分發給子View。此處由於out不處理Touch事件,故根據事件發生座標,將事件傳遞給out的直接子View(即middle)。
Middle及Center中事件處理過程同上。但是由於Center組件是clickable 表示其能處理Touch事件,故center中的onInterceptTouchEvent方法將事件傳遞給center自己的onTouchEvent方法處理。至此,此Touch事件已被處理,不繼續進行傳遞。
2.沒有指定誰會處理touch事件
布局檔案如下:
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical">
- android:id="@+id/view_out"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:background="#fff"
- android:gravity="center">
- android:id="@+id/view_mid"
- android:layout_width="300px"
- android:layout_height="400px"
- android:background="#f00"
- android:gravity="center">
- android:id="@+id/view_center"
- android:layout_width="150px"
- android:layout_height="150px"
- android:background="#000"
- android:gravity="center">
-
-
-
複製代碼
注意:只是比上一次的布局少了android:clickable="true"
接下來我們看一下列印的日誌
結合是上面的日誌,我們可以看一下ACTION_DOWN事件處理流程:
說明:
事件處理流程大致同上,區別是此狀態下,所有組件都不會處理事件,事件並不會被center的onTouchEvent方法“消費”,則事件會層層逆向傳遞迴到Activity,若Activity也不對此事件進行處理,此事件相當於消失了(無效果)。
對於後續的move、up事件,由於第一個down事件已經確定由Activity處理事件,故up事件由Activity的dispatchTouchEvent直接分發給自己的onTouchEvent方法處理。
代碼請看最後的附件
總結:
1) Touchevent 中,返回值是 true ,則說明消耗掉了這個事件,返回值是 false ,則沒有消耗掉,會繼續傳遞下去,這個是最基本的。
2) 事件傳遞的兩種方式:
隧道方式:從根項目依次往下傳遞直到最內層子項目或在中間某一元素中由於某一條件停止傳遞。
冒泡方式:從最內層子項目依次往外傳遞直到根項目或在中間某一元素中由於某一條件停止傳遞。 android對Touch Event的分發邏輯是View從上層分發到下層(dispatchTouchEvent函數)類似於隧道方式,然後下層優先開始處理Event(先mOnTouchListener,再onTouchEvent)並向上返回處理情況(boolean值),若返回true,則上層不再處理。類似於冒泡方式
於是難題出現了,你若把Touch Event都想辦法給傳到上層了(只能通過返回false來傳到上層),那麼下層的各種子View就不能處理後續事件了。而有的時候我們需要在下層和上層都處理Touch事件
舉個例子,ViewFlipper用來檢測手勢,在內部我們放幾個Image,有點像gallery的效果,也就是左右滑動切換圖片,但是圖片有時候我們希望可以放大縮小!這樣就會存在ViewFlipper裡面需要touch事件,而在image裡面也需要一個touch事件(當圖片大小螢幕邊界的時候可以拖動圖片,而不是左右切換圖片)。