前言
手機最跨時代的核心功能就在於click,所以可以說不了解click去觸發各種event的話,就有愧於作為一個Mobile 的開發者,這次就來淺淺的看看,android是如何使用MotionEvent去判斷各種手勢並觸發事件的
甚麼是 Motion Event
是一個觸碰事件封裝類的對象,其中封裝了該事件的所有信息,例如觸摸的位置、類型以及時間等。該對象會在用戶觸摸手機屏幕時被創建
簡單來說,我們在手機上的任何觸碰與操作都被它所包裝起來
而Android把觸碰事件區分為好幾種,分類的邏輯其實就是我們常用的手勢,像是點擊的按壓( ACTION_DOWN ) 與 放開 ( ACTION_UP )
- ACTION_DOWN : 手指初次觸碰螢幕,觸發
- ACTION_UP : 手指離開螢幕時,觸發
- ACTION_MOVE : 手指滑動時,會多次觸發
- ACTION_CANCEL : 事件被上層攔截時,觸發
- ACTION_OUTSIDE : 當事件發生在當前視圖的範圍之外時,觸發
像是我們最常使用的setOnClikerListener,就是在ACTION_DOWN 和ACTION_UP 之後才啟動,但光是按壓是不會啟動click event
那在這之前發生啥事呢?
點擊事件分發過程
每個事件的啟動,都是經由多重的判定與分發才得已執行,這是因為手勢的多樣與複雜,讓Android也設計了相對應的邏輯,去捕捉使用者正確的手勢,並順利執行想要的點擊事件
該過程主要由以下三個函數來完成:
onTouchEvent(MotionEvent ev)
→ 處理點擊事件,在dispatchTouchEvent
中調用,返回結果表示是否消耗當前點擊事件,並得已進行下一個事件
dispatchTouchEvent(MotionEvent ev)
→ 用來進行事件的分發
onInterceptTouchEvent(MotionEvent ev)
→ 用來判斷是否攔截某個事件
這邊就透過實際去設計監聽事件,來看android如何調派這些點擊事件
可以看到會先觸發onTouch()
,也就是手機接收到View正在被觸碰,接著放開按壓的手指,觸發第二次的Touch,最終啟動了Click事件
第一次是 ACTION_DOWN ,第二次是 ACTION_UP (當你手指按下屏幕並在屏幕上滑動時,還會有多次 ACTION_MOVE 的執行)。因此事件傳遞的順序是先經過onTouch()
,再傳遞到onClick()
View裡的 onTouchEvent 預設返回值是 true,當我們手指點選螢幕時候,先呼叫ACTION_DOWN事件,當onTouchEvent裡返回值是true的時候,onTouch會繼續呼叫ACTION_UP事件,如果onTouchEvent裡返回值是 false,那麼onTouchEvent只會呼叫ACTION_DOWN而不呼叫ACTION_UP
然後順便來看一下長按的話會是怎樣
會發現長按的過程中,沒有觸發action 1就啟動的LongClick
而且這兩個監聽touch事件都有一個共通處,那就是都要回傳Boolean
是為甚麼? 而且如果把回傳給予true,就無法觸發click功能了
點擊事件傳遞過程
想要知道發生什麼,就必須在往回看看在這之前,點擊事件判斷的流程是如何傳遞的
在上面一系列的過程中,我們知道了點擊事件的觸發與結束,中間會有一連串的事件發生,不單純只是click而已,而當中事件的主要功能就是dispatchTouchEvent
,稍微看一下源碼可以發現,這功能是源自於View的層級,當中就可以找到一些蛛絲馬跡
由於源碼很多,這邊只看比較重點的地方,定義了一個變數 result默認值是false,接著呼叫onFilterTouchEventForSecurity
這個方法主要作用就是判斷該觸及事件要不要分發,也就是去執行dispatchTouchEvent
所以接下來再往下追看看這個 onFilterTouchEventForSecurity
方法
源碼不多,看到當中先檢查View有沒有設置被遮擋時不處理觸摸事件的flag接著 再檢查受到該事件的窗口是否被其它窗口遮擋
所以我們就能知道 onFilterTouchEventForSecurity
方法是用來判斷當前 View 與其窗口有沒有被遮擋
接著回到前面的dispatchTouchEvent
如果前面是true
,那麼接下來會判斷 view 的OnTouchListener
是否有設置,也就是否有onTouch事件,並且這個View是不是可以點擊的,如果可以點擊並且OnTouchListener
不為空的話,就會繼續調用OnTouchListener.onTouch()
如果也是 true 的話,就給result
賦值為 true ,後面就不再調用view的點擊事件了。這就是前面說到onTouch()
的方法中回傳 true 後就不會再執行onClik
的原因
因為在onClik
之前會先判定OnTouchListener
的事件,而後不想元件觸發click事件就在OnTouchListener
當中回傳true,這樣就會直接跑到dispatchTouchEvent
,返回true並結束
這樣的過程大多是想設計一些攔截事件,也就是希望元件被觸碰(onTouch)的當下就執行某些邏輯,而不希望是在之後的點擊(onClik
)發生,就能透過OnTouchListener
回傳 true 去攔截點擊事件
總結
最後來整理一下,onClick從被使用者觸碰到執行的過程
- 使用者觸碰元件,開始向上層查找dispatchEvent(如下圖),如果中途遇到onIntercept被攔截,就會直接進入onTouchEvent。若沒被攔截最終會到View層級的dispatchTouchEvent,開始派發事件
其中 ViewGroup裡的onInterceptTouchEvent預設返回值是false,這樣 touch事件會傳遞到 View控制元件,ViewGroup裡的onTouchEvent預設返回值是false,也就是不像 View元件一樣消費當前的 Event,接收下一個的事件
2. 判斷當前的元件是否有設置onTouchListener ?
- 如果有,就執行onTouch,而在onTouch結束後回傳true就不會執行onClick
- 如果沒有,就繼續進行下去執行onTouchEvent
3. 判斷當前的View是否可以點擊( android:clickable = true) ?
4. 確認當前View的TouchEvent是否回傳為true (預設都是true)
- 是,執行ACTION_DOWN並判斷是否為長按事件 ?
* 長按事件 : 執行,若當中返回值為false,則會繼續執行ACTION_UP,最後也就會執行onClick方法 (這樣的設置下,長按與點擊事件都會觸發)
* 非長按事件 : 直接執行ACTION_UP - 不是,回傳 false並結束 dispatchTouchEvent
5. 執行ACTION_UP,判斷當前是否有監聽onClick()。不論有無都會執行dispatchEvent返回true
可以發現,只有在設置View無法點擊的狀態下,dispatchTouchEvent才會回傳false,代表並沒有任何的派發事件,雖然前面有觸碰到元件,但並沒有想觸發與使用者互動的事件
6. ACTION_UP 會去執行 performClick() 方法,當中觸發onClick
可以看到上面的 li.mOnClickListener.onClick(this),也就是我們常用的setOnClickListener(listener),所以在ACTION_UP(手觸碰放開的瞬間)
setOnClickListener
是官方給我們這些開發者封包過後的功能,能讓我們輕易的去設定每個元件的點擊事件,當中包含了許多觸發事件的判斷,而最終才到達的onClik
以上就是我對點擊事件的淺見~ 希望能多少幫助到大家