2015年3月25日 星期三

Android中的OnTouch事件和MotionEvent



原文:http://yizhi401.blog.51cto.com/6500239/1364958


要理解OnTouch機制,首先得明白有哪些OnTouch事件,常用的有這麼四種:






ACTION_DOWN


這個是OnTouchEvent事件的開始,任何事件都必須手指按下去才行。這個事件是一個從觸摸屏無觸摸狀態到有觸摸狀態的轉換。






ACTION_MOVE


緊接著的Move事件,可能有人會以為手指移動就會調用這個MOVE,但是經我測試並非如此,這個事件,只要手指在屏幕上,即使不動也會調用。






ACTION_UP


觸摸事件的結束,正常情況下,手指離開屏幕;觸摸屏從有觸摸狀態到無觸摸狀態的轉換。






ACTION_CANCEL


這個不是獨特的觸摸事件,而是由系統來判定的,一般認為你的手指從屏幕上你要點擊的區域移出來,就cancel掉了。很簡單的例子,你點擊一個按鈕,點擊的時候,按鈕變顏色,只要你的手指不拿開,按鈕不會變回原來的顏色。這時候你手指拿開了,按鈕顏色變回去,你就觸發了ACTION_UP的事件;或者你的手指移出按鈕區域了,按鈕顏色也變回去,這時候你觸發了ACTION_CANCEL事件,你點擊按鈕的方法也不會被觸發。






我們知道,android中的View是按照一個View Hierarchy來組織的。而且,在設定onTouch事件的時候,觸摸的順序也是走的這個View Hierarchy從上往下傳遞,叫做dispatch。






ViewGroup裡面有一個方法決定了這個ViewGroup的dispatch方式:






OnInterceptTouchEvent(MotionEvent ev)






這個方法由系統自己調用,但是在寫自己的ViewGroup的時候,可以復寫,工作過程和返回值說明如下:






Implement this method to intercept all touch screen motion events. This allows you to watch events as they are dispatched to your children, and take ownership of the current gesture at any point.


Using this function takes some care, as it has a fairly complicated interaction withView.onTouchEvent(MotionEvent) , and using it requires implementing that method as well as this one in the correct way. Events will be received in the following order:


You will receive the down event here.


The down event will be handled either by a child of this view group, or given to your own onTouchEvent() method to handle; this means you should implement onTouchEvent() to return true, so you will continue to see the rest of the gesture (instead of looking for a parent view to handle it). Also, by returning true from onTouchEvent(), you will not receive any following events in onInterceptTouchEvent() and all touch processing must happen in onTouchEvent() like normal.


For as long as you return false from this function, each following event (up to and including the final up) will be delivered first here and then to the target's onTouchEvent().


If you return true from here, you will not receive any following events: the target view will receive the same event but with the action ACTION_CANCEL , and all further events will be delivered to your onTouchEvent() method and no longer appear here.





Parameters



ev

The motion event being dispatched down the hierarchy.


Returns


Return true to steal motion events from the children and have them dispatched to this ViewGroup through onTouchEvent(). The current target will receive an ACTION_CANCEL event, and no further messages will be delivered here.










簡單來說,任何觸摸事件最初都是從這裡開始,也就是MotionEvent.ACTION_DOWN。如果這個方法返回true了,那麼這個onTouchEvent就不會被分配到子View了,而是在這個ViewGroup的OnTouchEvent事件裡面處理,而子View則會收到一個ACTION_CANCEL,從而結束其onTouchEvent方法的調用。


所以可以簡單記住,如果這個方法返回true,那麼就是說不要繼續dispatch事件了,所有後續的OnTouchEvent都留在這個ViewGroup裡面處理,子View獲得一個ACTION_CANCEL事件後就結束了。


如果這個方法返回false,那麼就是說ViewGroup不截斷onTouch事件,分發到相應的子View裡面處理。










子View處理onTouch事件的時候,需要有一個boolean型的返回值;這個返回值的實際意義其實是,該觸摸事件是否在這個onTouch方法裡面被消耗掉了。如果返回true,那麼就是被消耗掉了,該touch事件不會被繼續處理。如果返回false,就是說沒消耗掉,touch事件繼續被處理。






被處理?被誰處理?被這個子View的parent處理。就是說,Parent View不是把OnTouch事件分發到子View就沒事兒了,還要看看子View有沒有對這個事件處理,如果子View沒處理,他還得自己處理。所以綜上而言,一個MotionEvent首先是從ParentView到子View,然後還有可能再從子View返回到ParentView那邊去處理,是一個來回的過程。


是不是要從ParentView分發到子View,就看ParentView中OnInterceptTouchEvent事件的返回值;是不是要從子View返回到ParentView,就看子View的OnTouch事件返回值。






注意:一定不要把子View的onTouch事件返回值理解成是否要處理onTouch的後續事件。每一個MotionEvent都是獨立處理的,不會影響到其後續事件。






舉例子來說:ListView裡面有很多Item


這時候,如果我們點擊一個Item,那麼這個點擊事件首先被ListView接收,onInterceptTouchEvent返回false,分發到子View.然後子View來處理這個Touch事件。


這個時候,如果你的手指上下移動了(而不是左右),那麼這個手勢的含義就不是點擊而是你要滑動ListView了,所以這個時候,ListView的onInterceptTouchEvent就會返回true,你點擊的那個Item就收到了一個ACTION_CANCEL事件,以後你手指無論怎麼移動,都和Item沒關係,放到了ListView的OnTouch事件裡面處理了。所以這個時候你在子View裡面就不會監聽到ACTION_UP事件,要想找到這個事件,你得到ListView的OnTouch事件裡面去找了。