開篇語:最近程式在做一個小效果,要用到touch,結果整得雲裡面霧裡的,幹脆就好好把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事件
布局檔案如下:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical">
- <com.kris.touch.widget.TouchView
- android:id="@+id/view_out"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:background="#fff"
- android:gravity="center">
- <com.kris.touch.widget.TouchView
- android:id="@+id/view_mid"
- android:layout_width="300px"
- android:layout_height="400px"
- android:background="#f00"
- android:gravity="center">
- <com.kris.touch.widget.TouchView
- android:id="@+id/view_center"
- android:layout_width="150px"
- android:layout_height="150px"
- android:background="#000"
- android:gravity="center"
- android:clickable="true">
- </com.kris.touch.widget.TouchView>
- </com.kris.touch.widget.TouchView>
- </com.kris.touch.widget.TouchView>
- </LinearLayout>
複制代碼
注意: 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事件
布局檔案如下:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical">
- <com.kris.touch.widget.TouchView
- android:id="@+id/view_out"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:background="#fff"
- android:gravity="center">
- <com.kris.touch.widget.TouchView
- android:id="@+id/view_mid"
- android:layout_width="300px"
- android:layout_height="400px"
- android:background="#f00"
- android:gravity="center">
- <com.kris.touch.widget.TouchView
- android:id="@+id/view_center"
- android:layout_width="150px"
- android:layout_height="150px"
- android:background="#000"
- android:gravity="center">
- </com.kris.touch.widget.TouchView>
- </com.kris.touch.widget.TouchView>
- </com.kris.touch.widget.TouchView>
- </LinearLayout>
複制代碼
注意:隻是比上一次的布局少了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事件(當圖檔大小螢幕邊界的時候可以拖動圖檔,而不是左右切換圖檔)。
我首先的思路是着手于事件回傳的方式,研究了n久,實際了n久,都沒達到自己想要的結果 ,我甚至于把gallery和gallery3D 的源碼下載下傳下來看了N久也沒辦法去解決,在這裡随便說一下gallery吧,gallery雖然在這個效果,但是人家并不是ViewFlipper加image這樣來實作的,人家是像遊戲這樣用一個view來統一處理的,我們可以簡單的了解成自定義了一個控件,這樣touch事件想怎麼處理就怎麼處理,不過就是邏輯複雜了,我們想偷懶就沒辦法了,呵呵。。。
最後不停的試啊試啊,想到一個可行的方案,但是我覺得不是很靠譜,也就是:我們在ViewFlipper這裡,我們先把所有的touch都截取到,然後在他的onTouchEvent中,我們先調用imageview的onTouchEvent事件,如果傳回true,證明這個事件,imageview要用,那麼ViewFlipper就當什麼事都沒發生,如果imageview傳回的false,則調用自己的touchEvent.僞代碼如下:
- //自定義一個MyViewFlipper 繼承于ViewFlipper,并且實作onTouchEvent方式,
複制代碼
我覺得他不靠譜的原因為: 1. 他打斷了android的原有的機制,不是很提倡。
2. 得試先知道ViewFlipper裡面的控件,或者說通過某種路徑能擷取到
3. 如果ViewFlipper裡面的控件多了,就蛋疼了