Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android觸摸事件分發機制

Android觸摸事件分發機制

編輯:關於Android編程

Android中的事件分為按鍵事件和觸摸事件,這裡對觸摸事件進行闡述。Touch事件是由一個ACTION_DOWN,n個ACTION_MOVE,一個ACTION_UP組成onClick,onLongClick,onScroll等事件。Android中的控件都是繼承View這個基類的,而控件分為兩種:一種是繼承View不能包含其他控件的控件;一種是繼承ViewGroup可以包含其他控件的控件,暫且稱為容器控件,比如ListView,GridView,LinearLayout等。

 

 

這裡先對幾個函數講解下。

?public boolean dispatchTouchEvent (MotionEventev) 這個方法分發TouchEvent

?public booleanonInterceptTouchEvent(MotionEvent ev)這個方法攔截TouchEvent

?public boolean onTouchEvent(MotionEvent ev) 這個方法處理TouchEvent

其中view類中有dispatchTouchEvent和onTouchEvent兩個方法,ViewGroup繼承View,而且還新添了一個onInterceptTouchEvent方法。Activity中也無onInterceptTouchEvent方法,但有另外兩種方法。我們可以發現上面3個方法都是返回boolean,那各代表什麼意思呢?

 

public boolean dispatchTouchEvent (MotionEvent ev)

Activity中解釋:

Called to process touch screen events.You can override this to intercept all touch screen events before they aredispatched to the window. Be sure to call this implementation for touch screenevents that should be handled normally.

Parameters

ev

The touch screen event.

Returns

·boolean Return true if this event was consumed.

它會被調用處理觸摸屏事件,可以重寫覆蓋此方法來攔截所有觸摸屏事件在這些事件分發到窗口之前。通常應該處理觸摸屏事件,一定要調用這個實現。當返回值為true時,表示這個事件已經被消費了。例如在TextActivity中dispatchTouchEvent在ACTION_MOVE返回true,運行結果如下:

\

 

也就是它並沒有把那ACTION_MOVE分發下去。

 

public boolean onInterceptTouchEvent (MotionEvent ev)

Implementthis method to intercept all touch screen motion events. This allows you towatch events as they are dispatched to your children, and take ownership of thecurrent gesture at any point.

Usingthis 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 thecorrect way. Events will be received in the following order:

1.You will receive the down event here.

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

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

4.If you return true from here, you will not receive any followingevents: the target view will receive the same event but with the actionACTION_CANCEL, and all further events will be delivered to youronTouchEvent() method and no longer appear here.

Parameters

ev

The motion event being dispatched down the hierarchy.

Returns

·Return true to steal motionevents from the children and have them dispatched to this ViewGroup throughonTouchEvent(). The current target will receive an ACTION_CANCEL event, and nofurther messages will be delivered here.

基本意思就是:

1. ACTION_DOWN首先會傳遞到onInterceptTouchEvent()方法

2.如果該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成之後return false,那麼後續的move, up等事件將繼續會先傳遞給該ViewGroup,之後才和down事件一樣傳遞給最終的目標view的onTouchEvent()處理。

3.如果該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成之後return true,那麼後續的move, up等事件將不再傳遞給onInterceptTouchEvent(),而是和down事件一樣傳遞給該ViewGroup的onTouchEvent()處理,注意,目標view將接收不到任何事件。

4.如果最終需要處理事件的view的onTouchEvent()返回了false,那麼該事件將被傳遞至其上一層次的view的onTouchEvent()處理。

5.如果最終需要處理事件的view的onTouchEvent()返回了true,那麼後續事件將可以繼續傳遞給該view的onTouchEvent()處理。

Android touch事件傳遞機制:

我們可以看看android源代碼:

Activity.java中

\

 

暫且不管onUserInteraction方法因為它只是一個空方法如果你沒實現的話。getWindow().superDispatchTouchEvent(ev)。其中getWindow()返回的是PhoneWindow。

PhoneWindow.java:

\

 

此函數調用super.dispatchTouchEvent(event),Activity的rootview是PhoneWindow.DecorView,它繼承FrameLayout。通過super.dispatchTouchEvent把touch事件派發給各個Activity的是子view。同時我可以看到,如果子view攔截了事件,則不會執行onTouchEvent函數。

 

 

 

ViewGroup.java中dispatchTouchEvent方法:

由於代碼過長這裡就不貼出來了,但也知道它返回的是

return target.dispatchTouchEvent(ev);

這裡target指的是所分發的目標,可以是它本身,也可以是它的子View。

 

ViewGroup.java中的onInterceptTouchEvent方法:

\

 

默認情況下返回false。即不攔截touch事件。

 

 

View.java中的dispatchTouchEvent方法

\

 

這裡我們很清楚可以知道如果if條件不成立則dispatchTouchEvent的返回值是onTouchEvent的返回值

View.java中的onTouchEvent方法

\

 

 

所以很容易得到觸摸事件默認處理流程(以ACTION_DOWN事件為例):

\

 

當觸摸事件ACTION_DOWN發生之後,先調用Activity中的dispatchTouchEvent函數進行處理,緊接著ACTION_DOWN事件傳遞給ViewGroup中的dispatchTouchEvent函數,接著viewGroup中的dispatchTouchEvent中的ACTION_DOWN事件傳遞到調用ViewGroup中的onInterceptTouchEvent函數,此函數負責攔截ACTION_DOWN事件。由於viewGroup下還包含子View,所以默認返回值為false,即不攔截此ACTION_DOWN事件。如果返回false,則ACTION_DOWN事件繼續傳遞給其子view。由於子view不是viewGroup的控件,所以ACTION_DOWN事件接著傳遞到onTouchEvent進行處理事件。此時消息的傳遞基本上結束。從上可以分析,motionEvent事件的傳遞是采用隧道方式傳遞。隧道方式,即從根元素依次往下傳遞直到最內層子元素或在中間某一元素中由於某一條件停止傳遞。

接下來繼續分析,事件的處理。剛才ACTION_DOWN事件傳遞到view的onTouchEvent函數中處理了,默認是返回true,接著view的dispatchTouchEvent返回true,再接著viewGroup的dispatchTouchEvent返回true,最後Activity的dispatchTouchEvent返回true。我們發現,motionEvent事件的處理采用冒泡方式。冒泡方式,從最內層子元素依次往外傳遞直到根元素或在中間某一元素中由於某一條件停止傳遞。

下圖為程序調試結果:

ACTION_DOWN事件輸出:

\

 

ACTION_MOVE事件輸出:

\

 

現在我們來做一些改變,就接著以ACTION_DOWN為例

情況一:

我們在View中onTouchEvent中ACTION_DOWN返回false,輸出結果如下:

\

 

可以發現ACTION_DOWN事件傳遞到上層的ViewGroup的onTouchEvent,同時返回true,說明事件被ViewGroup消費了。同時之後的touch事件(ACTION_MOVE等)不再傳遞給view,只傳遞到ViewGroup,由ViewGroup的onTouchEvent函數處理touch事件。同時onInterceptTouchEvent也不再調用。

情況二:

我們在View中onTouchEvent中ACTION_MOVE返回false,輸出結果如下:

\

 

由於view未消費此ACTION_MOVE事件,按照原理來說應該是將事件處理冒泡到ViewGroup去處理,但結果卻是Activity處理的。我們知道,觸摸事件首先發生的就是ACTION_DOWN事件,我們在onInterceptTouchEvent所解釋就可以發現ACTION_DOWN與ACTION_MOVE等事件有區別,ACTION_DOWN事件作為起始事件,它的重要性是要超過ACTION_MOVE和ACTION_UP的,如果發生了ACTION_MOVE或者ACTION_UP,那麼一定曾經發生了ACTION_DOWN。也就是說ACTION_DOWN事件被view消費了,而ACTION_MOVE事件沒被消費,傳遞到ViewGroup,由於之前ViewGroup沒處理ACTION_DOWN事件,所以它也不處理ACTION_MOVE。但Activity卻不一樣,它可以接受所有事件。

情況三:

這次在ViewGroup中的onInterceptTouchEvent中ACTION_DOWN返回true

結果如下:

\

 

它直接把事件發送給ViewGroup的onTouchEvent處理,此後不再攔截事件直接到viewGroup中的onTouchEvent處理。

情況四:

在ViewGroup中的onInterceptTouchEvent中ACTION_MOVE返回true

結果如下:

\

 

ACTION_MOVE被ViewGroup攔截了,上次處理ACTION_DOWN的view則會收到ACTION_CANCEL事件,之後ViewGroup不再攔截後續事件,事件直接在ViewGroup中的onTouchEvent處理。

還有很多情況,這裡不一一列出了。

 

在寫這個demo一開始,我發現重寫了onTouchEvent函數就無法獲取onClick和onLongClick事件。接下來討論當重寫了onTouchEvent,android是如何區分onClick,onLongClick事件的。搞清楚此問題對於如何響應UI各種事件是很重要的,例如類似android桌面的應用程序圖標,可以點擊,然後長按拖動。

Android中onclick,onLongClick是都是由ACTION_DOWN,ACTION_UP組成。如果在同一個View中onTouchEvent、onclick、onLongClick都進行了重寫。onTouchEvent最先捕獲ACTION_DOWN、ACTION_UP等單元事件。接下來才可能發生onClick、onLongClick事件。一個onclick事件是由ACTION_DOWN和ACTION_UP組成的。一個onLongClick事件至少有一個ACTION_DOWN。那android具體是怎麼實現的呢,可以看源代碼:

View.java中:

 

上面我已經展示了onTouchEvent方法,但由於過長我折疊了一部分代碼,現在展開

\

 

這個if條件內執行就是click事件處理及longClick事件處理。先看ACTION_DOWN事件

\

 

我們看到有個postDelayed方法,此方法意思為延時把線程插入到消息隊列。即ACTION_DOWN後觸發一個postDelayed方法。mPendingCheckForTap屬於CheckForTap的實例。

\

 

在裡面開啟一個線程當為LONG_CLICKABLE,調用postCheckForLongClick方法。

\

 

再看mPendingCheckForLongPress這個線程。

\

 

 

當上面一系列條件全都符合的情況就調用performLongClick方法。

\

此方法就調用我們熟悉的onLongClick函數。

 

至此onLongClick事件已經分析完。再接著看ACTION_UP事件

\

 

 

直接關注performClick函數:

\

 

這裡我們同樣看到了我們熟悉的onClick方法。

所以android這種機制是保證了此onClick和onLongClick能與onTouchEvent並存。接下來考慮onclick與onLongClick是否並存,其實這個問題前面已經闡述了。只要此事件沒被消費,它還會接著傳遞下去。從上面知道onLongClick是在單獨的線程執行,發生在ACTION_UP之前。Onclick發生在ACTION_UP之後,也就是說,如果在onLongClick返回false,onclick就會發生,而onlongClick返回true,則代表此事件已經被消費。Onclick不再發生。

返回false:

\

 

返回true

\

 

如果多次設置onclick事件,則最頂層的onclick覆蓋掉底層onclick事件;多次設置onLongClick事件,則只執行底層view的onLongClick方法。當ACTION_DOWN調用之後返回false。

 

 

可以看到ACTION_DOWN被消費了,所以不會讓上層處理了。

\

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved