從MotionEvent 來看看android如何實現點擊功能

Voss
Jun 14, 2022

--

前言

手機最跨時代的核心功能就在於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從被使用者觸碰到執行的過程

  1. 使用者觸碰元件,開始向上層查找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

以上就是我對點擊事件的淺見~ 希望能多少幫助到大家

--

--

Voss
Voss

Written by Voss

a Freelancer , a Software Engineer , a Reader , a Body Builder

No responses yet