研究了一下android的touch事件,從doc到google,算是有了一些初步的理解。以下是經過消化的個人理解,有可能與事實不符,歡迎指正。
首先,來了解一下android的事件機制。android的基本元事件我猜應該有5種,理由是MotionEvent類里有5個事件常量,分別是ACTION_DOWN,ACTION_MOVE,ACTION_UP,ACTION_CANCEL和ACTION_OUTSIDE。其中DOWN/MOVE/UP是人為觸發的,CANCEL是系統觸發,至於OUTSIDE,doc里寫是當事件發生在UI元素之外的時候出發,但實際上我還從來沒有成功觸發過這個事件。也許你會覺得按鈕按下,然后移出按鈕邊界會觸發這個事件,但很遺憾實際上不會。
也就是說,其實我們能夠用到的元事件只有三種,DOWN/MOVE/UP,就像三原色調配出多彩的世界,android其余的復雜事件都是由這三個元事件組合而成的。比如scroll(滾動)/fling(就是DOWN然后快速的MOVE一小段距離然后UP)/longpress(長按)/singletapup(單擊)等等……
那么系統是怎么響應這些事件的呢?響應事件的方法有幾種。
最簡單也是最常用的是實現一個OnClickListener的接口,然后用view的setOnClickListener方法綁定這個接口,就可以處理view的單擊事件。這種方法簡便易行,但是只能處理單擊這一種事件。實際上,click這個事件是順序觸發ACTION_DOWN和ACTION_UP兩個事件組成的,中間可以存在ACTION_MOVE,但不能MOVE出view的邊界,不然即使再MOVE回來也不能觸發click事件了。OnClickListener接口的onClick方法是在click事件的ACTION_UP的timing執行。onClick方法只有一個參數,就是被單擊的View,這種方法是不能獲取單擊事件的,也就無法獲取單擊點的坐標等屬性。
第二種略微復雜一點的事件處理方法是用OnTouchListener接口而不是OnClickListener,實現方法跟上面一種差不多,實現這個接口的onTouch(View v, MotionEvent e)方法之后,就可以響應touch的元事件了,注意,是元事件!也就是說,一次簡單的click事件會在DOWN和UP的時候分別執行onTouch方法各一次(如果是scroll一下會觸發一次DOWN和很多次的MOVE)。同時,由於有了MotionEvent的參數,我們可以用e.getAction()來獲取元事件類型或者e.getX()/e.getY()來獲取事件發生點的坐標,以及許多其他的事件屬性。這樣,能干的事情就多那么一點點了。這個方法還有一個boolean的返回值,至於這個返回值是做什么用的,一會兒再交代。
第三種是連接口都用不着,而是直接覆寫一個現成的方法view.onTouchEvent(MotionEvent e),這個方法和onTouch方法的不同,直觀上來講,沒有view參數了。那這個方法不就沒有onTouch方法強大了?那要它有啥用呢?這就要從android的事件傳播機制講起了。
android的事件傳播機制跟網頁W3C的標准有一點點類似,都是一個事件產生之后經過一個由上到下的捕捉過程再經過一個由下到上的冒泡過程。但是不同的是,android事件在傳播過程中的某一層如果被消費了,就會終止傳播。也就是說,發生在一個按鈕上的ACTION_DOWN事件實際上是先發生在他的父父父父控件(某一個ViewGroup)上然后再層層傳過來的,按鈕如果消費這個事件,那么傳播終止(如果他爸爸消費了這個事件,那么按鈕就悲催地根本獲取不到這個事件),如果它不消費這個事件,那么這個事件又會冒泡回去,過程大致是這樣。
具體來講,android的事件產生以后,捕捉過程是由ViewGroup.onIntercepTouchEvent(MotionEvent e)傳遞的,即產生之后,由上向下,依次傳遞給子ViewGroup的onInterceptTouchEvent方法。這個方法有個boolean的返回值,false表示本人不消費這個事件,這個事件繼續傳給我兒子。true表示本人留下這個事件啦,捕捉過程到此為止,不傳給兒子了。兒子的onInterceptTouchEvent方法就不會再執行了。注意,這個方法只有在ViewGroup里有,而最孫子的View里是沒有的,因為到View為止,捕捉過程一定會結束。
那么,捕捉過程終止過后,冒泡過程是由誰來處理的呢?答案就是onTouchEvent方法。當捕捉過程達到最孫子的View(我知道應該叫葉子View……),或者某一層的ViewGroup的onInterceptTouchEvent返回值為true的時候,該View或者該ViewGroup的onTouchEvent方法就會執行,這個方法同樣會返回一個boolean,false代表我不消費這個事件,這個事件冒泡回去孝敬我爸爸,true表示我消費啦,事件傳播就此終止。而冒泡回去的事件,就由上一層的onTouchEvent來處理。這就是說,網頁的事件是捕捉冒泡走一條線,並且默認不會被消費而阻斷的(關於網頁事件的流程,感謝stauren提供技術支持),而android的事件則是捕捉冒泡分兩個方法線來完成,一旦某一個節點消費了這個事件,事件就停止傳播了。
寫到這里,有人可能會發現一個問題。什么叫“事件被消費了”?不就是事件被處理了么?如果我把事件處理的方法寫在onInterceptTouchEvent/onTouchEvent里,但是返回值卻是false,那么不就做到,既處理了事件,又沒有停止事件傳播,不就跟網頁一樣了么?(這種需求實際工作中非常有用!)
願望是好的,但實際操作一下,你就會發現,除了ACTION_DOWN事件以外,其余的事件你都只有返回值是true的那個方法才能捕捉的到。這是為什么呢?為什么DOWN事件又可以呢?這是因為,android的事件傳播只有在第一個事件發生的時候(所有事件第一個發生都是DOWN,你得先把手按上去嘛……)按前述順序進行一次,找到那個返回值是true的方法,然后,所有后續事件都會直接傳給那個View,不再經歷中間的傳播過程!所以,中間路徑上的那些返回false的方法,只能捕捉到每次的第一個DOWN事件,而后續的MOVE和UP事件就捕捉不到了。
那么有沒有辦法能夠讓消費之后傳播過程繼續呢?有,只要你在返回true的方法里人為調用上一層的onTouchEvent或者下一層的onInterceptTouchEvent,傳播不就進行下去了么?
最后,處理復雜的三元事件的組合事件,android提供了一個GestureDetector類,實現GestureDetector.GestureListener接口之后(就是各種onLongPress/onFling/onScroll等方法)並實例化一個GestureDetector對象之后,就可以在本來要處理事件的onTouchEvent方法里人工調用這個detector實例自帶的onTouchEvent方法,改由detector來處理事件,於是各種復雜事件就能被識別和處理了。
另外寫了個小例子,在資源里面,因為缺資源分下載資料,所以定了個1分,敬請各位諒解……謝謝!
-------------------------------------------------------------------------------------------------------------------------------------------------------
原貼地址:http://blog.csdn.net/lingang1359/article/details/7046071
