1. 程式人生 > >Android Touch事件傳遞機制解析

Android Touch事件傳遞機制解析

// 表示事件是否攔截, 返回false表示不攔截
	@Override
	public boolean onInterceptTouchEvent(MotionEvent arg0) {
		return false;
	}

	/**
	 * 重寫onTouchEvent事件,什麼都不用做
	 */
	@Override
	public boolean onTouchEvent(MotionEvent arg0) {
		return false;
	}


/**
	 * 事件分發, 請求父控制元件及祖宗控制元件是否攔截事件
	 */
	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		if (getCurrentItem() != 0) {
			getParent().requestDisallowInterceptTouchEvent(true);// 用getParent去請求,
																	// 不攔截
		} else {// 如果是第一個頁面,需要顯示側邊欄, 請求父控制元件攔截
			getParent().requestDisallowInterceptTouchEvent(false);// 攔截
		}
		return super.dispatchTouchEvent(ev);
	}


開篇語:最近程式在做一個小效果,要用到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。

device-2012-03-24-084959.png

說明: 白色為最外層,它佔滿整個螢幕;

紅色為中間區域,屬於白色中的一層;

黑色為中心區域,必於紅色中的一層。

注意:他們本質上是:LinearLayout,而不是RelativeLayout或者其它佈局。

1.由中心區域處理touch事件

佈局檔案如下:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.     android:layout_width="fill_parent"
  4.     android:layout_height="fill_parent"
  5.     android:orientation="vertical">
  6.     <com.kris.touch.widget.TouchView
  7.         android:id="@+id/view_out"
  8.         android:layout_width="fill_parent"
  9.         android:layout_height="fill_parent"
  10.         android:background="#fff"
  11.         android:gravity="center">
  12.             <com.kris.touch.widget.TouchView
  13.                 android:id="@+id/view_mid"
  14.                 android:layout_width="300px"
  15.                 android:layout_height="400px"
  16.                 android:background="#f00"
  17.                 android:gravity="center">
  18.             <com.kris.touch.widget.TouchView
  19.                 android:id="@+id/view_center"
  20.                 android:layout_width="150px"
  21.                 android:layout_height="150px"
  22.                 android:background="#000"
  23.                 android:gravity="center"
  24.                 android:clickable="true">
  25.             </com.kris.touch.widget.TouchView>
  26.             </com.kris.touch.widget.TouchView>
  27.     </com.kris.touch.widget.TouchView>
  28. </LinearLayout>

複製程式碼

注意:                android:clickable="true"  

接下來我們看一下列印的日誌:

1111.png

結合是上面的日誌,我們可以看一下ACTION_DOWN事件處理流程:

a0dfaa98gb7f95585f7a2&690.png

說明:

首先觸控事件發生時(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事件

佈局檔案如下:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.     android:layout_width="fill_parent"
  4.     android:layout_height="fill_parent"
  5.     android:orientation="vertical">
  6.     <com.kris.touch.widget.TouchView
  7.         android:id="@+id/view_out"
  8.         android:layout_width="fill_parent"
  9.         android:layout_height="fill_parent"
  10.         android:background="#fff"
  11.         android:gravity="center">
  12.             <com.kris.touch.widget.TouchView
  13.                 android:id="@+id/view_mid"
  14.                 android:layout_width="300px"
  15.                 android:layout_height="400px"
  16.                 android:background="#f00"
  17.                 android:gravity="center">
  18.             <com.kris.touch.widget.TouchView
  19.                 android:id="@+id/view_center"
  20.                 android:layout_width="150px"
  21.                 android:layout_height="150px"
  22.                 android:background="#000"
  23.                 android:gravity="center">
  24.             </com.kris.touch.widget.TouchView>
  25.             </com.kris.touch.widget.TouchView>
  26.     </com.kris.touch.widget.TouchView>
  27. </LinearLayout>

複製程式碼

注意:只是比上一次的佈局少了android:clickable="true" 
接下來我們看一下列印的日誌

2222.png

結合是上面的日誌,我們可以看一下ACTION_DOWN事件處理流程:

a0dfaa98gb7f9559d8155&690.png

說明:

事件處理流程大致同上,區別是此狀態下,所有元件都不會處理事件,事件並不會被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.虛擬碼如下:

  1. //自定義一個MyViewFlipper 繼承於ViewFlipper,並且實現onTouchEvent方式,

複製程式碼

我覺得他不靠譜的原因為: 1. 他打斷了android的原有的機制,不是很提倡。 
2. 得試先知道ViewFlipper裡面的控制元件,或者說通過某種路徑能獲取到 
3. 如果ViewFlipper裡面的控制元件多了,就蛋疼了

好了,如果有什麼意見或者建議,大家一起討論。