Android Touch事件傳遞機(jī)制解析
沒事逛論壇,無意間看到了一篇非常不錯(cuò)的帖子,轉(zhuǎn)載如下:
開篇語:最近程序在做一個(gè)小效果,要用到touch,結(jié)果整得云里面霧里的,干脆就好好把a(bǔ)ndroid touch機(jī)制好好看了一下,呵呵。。
android系統(tǒng)中的每個(gè)ViewGroup的子類都具有下面三個(gè)和TouchEvent處理密切相關(guān)的方法:
1)public boolean dispatchTouchEvent(MotionEvent ev) 這個(gè)方法用來分發(fā)TouchEvent
2)public boolean onInterceptTouchEvent(MotionEvent ev) 這個(gè)方法用來攔截TouchEvent
3)public boolean onTouchEvent(MotionEvent ev) 這個(gè)方法用來處理TouchEvent
注 意:不是所有的View的子類,很多教程都說的是所有的View的子類,只有可以向里面添加View的控件才需要分發(fā),比如TextView它本身就是最 小的view了,所以不用再向它的子視圖分發(fā)了,它也沒有子視圖了,所以它沒有dispatch和Intercept,只有touchEvent。
說明:
白色為最外層,它占滿整個(gè)屏幕;
紅色為中間區(qū)域,屬于白色中的一層;
黑色為中心區(qū)域,必于紅色中的一層。
注意:
他們本質(zhì)上是:LinearLayout,而不是RelativeLayout或者其它布局。
1.由中心區(qū)域處理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>
注意:
接下來我們看一下打印的日志:
結(jié)合是上面的日志,我們可以看一下ACTION_DOWN事件處理流程:
說明:
首先觸摸事件發(fā)生時(shí)(ACTION_DOWN),由系統(tǒng)調(diào)用Activity的dispatchTouchEvent方法,分發(fā)該事件。根據(jù)觸摸事件的坐 標(biāo),將此事件傳遞給out的dispatchTouchEvent處理,out則調(diào)用onInterceptTouchEvent 判斷事件是由自己處理,還是繼續(xù)分發(fā)給子View。此處由于out不處理Touch事件,故根據(jù)事件發(fā)生坐標(biāo),將事件傳遞給out的直接子View(即 middle)。
Middle及Center中事件處理過程同上。但是由于Center組件是clickable 表示其能處理Touch事件,故center中的onInterceptTouchEvent方法將事件傳遞給center自己的 onTouchEvent方法處理。至此,此Touch事件已被處理,不繼續(xù)進(jìn)行傳遞。
2.沒有指定誰會(huì)處理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"
接下來我們看一下打印的日志
結(jié)合是上面的日志,我們可以看一下ACTION_DOWN事件處理流程:
說明:
事件處理流程大致同上,區(qū)別是此狀態(tài)下,所有組件都不會(huì)處理事件,事件并不會(huì)被center的onTouchEvent方法“消費(fèi)”,則事件會(huì)層層逆向傳遞回到Activity,若Activity也不對(duì)此事件進(jìn)行處理,此事件相當(dāng)于消失了(無效果)。
對(duì)于后續(xù)的move、up事件,由于第一個(gè)down事件已經(jīng)確定由Activity處理事件,故up事有由Activity的dispatchTouchEvent直接分發(fā)給自己的onTouchEvent方法處理。
代碼請看最后的附件
總結(jié):
1) Touchevent 中,返回值是 true ,則說明消耗掉了這個(gè)事件,返回值是 false ,則沒有消耗掉,會(huì)繼續(xù)傳遞下去,這個(gè)是最基本的。
2) 事件傳遞的兩種方式:
隧道方式:從根元素依次往下傳遞直到最內(nèi)層子元素或在中間某一元素中由于某一條件停止傳遞。
冒泡方式:從最內(nèi)層子元素依次往外傳遞直到根元素或在中間某一元素中由于某一條件停止傳遞。 android對(duì)Touch Event的分發(fā)邏輯是View從上層分發(fā)到下層(dispatchTouchEvent函數(shù))類似于隧道方式,然后下層優(yōu)先開始處理Event(先 mOnTouchListener,再onTouchEvent)并向上返回處理情況(boolean值),若返回true,則上層不再處理。類似于冒泡方式。
于是難題出現(xiàn)了,你若把Touch Event都想辦法給傳到上層了(只能通過返回false來傳到上層),那么下層的各種子View就不能處理后續(xù)事件了。而有的時(shí)候我們需要在下層和上層都處理Touch事件
舉個(gè)例子,ViewFlipper用來檢測手勢,在內(nèi)部我們放幾個(gè)Image,有點(diǎn)像gallery的效果,也就是左右滑動(dòng)切換圖片,但是圖片有時(shí)候我們希 望可以放大縮?。∵@樣就會(huì)存在ViewFlipper里面需要touch事件,而在image里面也需要一個(gè)touch事件(當(dāng)圖片大小屏幕邊界的時(shí)候可 以拖動(dòng)圖片,而不是左右切換圖片)。
我首先的思路是著手于事件回傳的方式,研究了n久,實(shí)際了n久,都沒達(dá)到自己想要的結(jié)果 ,我甚至于把gallery和gallery3D 的源碼下載下來看了N久也沒辦法去解決,在這里隨便說一下gallery吧,gallery雖然在這個(gè)效果,但是人家并不是ViewFlipper加 image這樣來實(shí)現(xiàn)的,人家是像游戲這樣用一個(gè)view來統(tǒng)一處理的,我們可以簡單的理解成自定義了一個(gè)控件,這樣touch事件想怎么處理就怎么處 理,不過就是邏輯復(fù)雜了,我們想偷懶就沒辦法了,呵呵。。。
最后不停的試啊試啊,想到一個(gè)可行的方案,但是我覺得不是很靠譜,也就是:我們在ViewFlipper這里,我們先把所有的touch都截取到,然后在 他的onTouchEvent中,我們先調(diào)用imageview的onTouchEvent事件,如果返回true,證明這個(gè)事件,imageview要 用,那么ViewFlipper就當(dāng)什么事都沒發(fā)生,如果imageview返回的false,則調(diào)用自己的touchEvent.偽代碼如下:
//自定義一個(gè)MyViewFlipper 繼承于ViewFlipper,并且實(shí)現(xiàn)onTouchEvent方式.
我覺得他不靠譜的原因?yàn)椋?/p>
1. 他打斷了android的原有的機(jī)制,不是很提倡。
2. 得試先知道ViewFlipper里面的控件,或者說通過某種路徑能獲取到。
3. 如果ViewFlipper里面的控件多了,就蛋疼了。
好了,如果有什么意見或者建議,大家一起討論。