窺探Android Touch事件內幕系列之一

最近在使用Robolectric進行單元測試的時候經常需要模擬click、touch等UI操作,期間遇到了各種問題,例如:onTouch和onTouchEvent有什么區別?onClick什么時候會響應?使用ShadowView中clickOn模擬點擊操作為什么無法響應onTouch?為什么ListView添加滑動菜單功能后,ListView本身不能再滑動?被這些問題困擾了很久,因此決定徹底搞清楚Android Touch事件分發機制。這篇文章先整體介紹了Android UI事件處理機制-基于監聽的處理方式和基于回調的處理方式,然后分別從View、ViewGoup角度分析Touch事件背后的實現邏輯,最后結合Robolectric介紹如何在單元測試中測試相關的回調、監聽方法。

Android UI事件處理機制

事件是指一個對象的狀態發生變化,事件處理是指事件發生時執行的代碼。與界面編程相關的就是UI事件-用戶在程序界面中執行各種操作,之后應用程序需要對這些操作提供響應動作,這個響應就是由UI事件的處理機制來完成。

Android針對UI事件,提供了兩種處理機制:一種是基于監聽方式的處理機制,另一種是基于回調方式的處理機制。對于基于監聽的處理方式,主要是為Android 界面組件綁定特定的事件監聽器;對于基于回調的處理方式,主要的做法是通過為Android 組件重寫?特定的回調方法實現。下面我們詳細介紹二者具體模型、流程、使用方法。

基于監聽的事件處理機制

基于監聽的事件處理方式實際是一種委托式(代理)模式,某個組件(事件源)將整個事件的處理委托給特定的對象(綁定在該組件上的事件監聽器),由這個特定的對象來進行該事件的響應。

事件監聽處理模型

事件監聽處理模型涉及到三類對象:

  • 事件源-事件產生的地方,一般就是界面組件。
  • 事件-事件封裝了界面組件上的一次用戶操作,如果程序需要獲取界面組件上所發生事件的相關信息,就可以通過Event對象來獲取。
  • 事件監聽器-包含事件處理方法,負責監聽事件源所發生的事情,并對各種事件做出響應。
事件處理流程

簡單來說,基于監聽器的事件處理流程是事件源組件發生事件,系統會執行該事件源組件上監聽器的對應處理方法。具體流程如下圖所示。

基于監聽的事件處理流程.png
常見的事件監聽器實現方式

在事件處理模型三個重要組成部分中,事件由系統負責生成、任意界面組件都可作為事件源,而事件監聽器是整個事件處理核心。因此我們主要的工作就是實現事件監聽器。所謂實現事件監聽器其實就是實現特定接口的Java類實例,常用的方法包括:

  • 內部類實現

    內部類實現事件監聽器的優點是可以在當前外部類中復用該監聽器類、監聽器類是外部類的內部類,可以自由訪問外部類的所有界面組件。

  • 外部類實現

    使用外部類定義事件監聽器的形式比較少見,使用外部類實現的缺點主要是事件監聽器屬于特定GUI,定義成外部類不利于提高程序內聚性、外部類形式的事件監聽器不能自由訪問創建GUI界面的類中組件,編程不夠簡潔。

  • Activity實現

    直接在Activity中實現監聽器接口,這種方式雖然簡單,但是卻使得Activity類的職能混亂,Activity主要職責是完成界面初始化工作,但此時還需包含事件處理器方法,違反面向對象單職能原則。

  • 匿名內部類實現

    使用匿名內部類實現,代碼簡潔,是目前使用最廣泛的方法。

基于回調的事件處理機制

與監聽的委托式事件處理不一樣,基于回調事件處理模型,事件源和事件監聽器是統一的。用戶在組件上激發某個事件,組件自己特定的方法將會負責處理該事件。通常實現的方法是通過繼承組件類,重寫相關的事件處理方法。

基于回調的事件傳播

基于回調的事件處理方法都有一個boolean返回值,使用該返回值標識該回調方法是否已經完全消費該事件。

  • true-該處理方法已經完全處理事件,該事件不會傳播出去
  • false-該處理方法并未完全處理該事件,該事件會傳播出去

二者對比

基于監聽器的事件處理機制優點:

  • 事件源、事件監聽器由兩個類實現,使用委托(代理)模式,分工明確,易于維護。
  • Android事件處理機制優先觸發該組件綁定的事件監聽器,然后才會觸發該組件提供的事件回調方法。即監聽機制優先級更高。

基于回調的事件處理機制優點:

  • 適合處理事件邏輯比較固定的View

Android Touch 事件分發、響應機制

Android UI事件包括Touch事件、Key事件等,我們這里主要研究Touch事件分發、消費的流程。Touch事件分發消費流程中包括兩個主角:View和ViewGroup(Activity的Touch事件實際上是調用它內部的ViewGroup的Touch事件)。Touch事件類型包括ACTION_DOWN,ACTION_UP,ACTION_MOVE。這里我們通過解析View、ViewGroup事件分發響應的源碼,來對Android Touch事件的分發進行研究。

View Touch事件響應詳細流程

View中關于Touch事件主要涉及兩個方法:dispatchTouchEvent、onTouchEvent。Android View 事件分發機制 源碼解析 (上)中結合實例及源碼對這兩個方法進行了詳細研究,具體細節可參考該文章。

dispatchTouchEvent

View的dispatchTouchEvent主要就是將事件按照onTouchListener監聽器、onTouchEvent事件回調的順序依次分發。任意一個返回true則表示該事件已經被當前這個View處理了。再回過頭來看上面我們提到的Android提供的兩種事件處理方法優缺點中『Android事件處理機制優先觸發該組件綁定的事件監聽器,然后才會觸發該組件提供的事件回調方法。即監聽機制優先級更高。』,這段話是不是更能理解了?


View的dispatchTouchEvent流程圖.png
onTouchEvent

View的onTouchEvent是Android系統處理Touch事件的回調方法,這個方法針對不同事件類型-ACTION_DOWN、ACTION_MOVE、ACTION_UP分別進行處理,我們下面也按照這三種事件類型進行分析。

  • ACTION_DOWN:更新View狀態、發送一個檢測長按的延時任務(500ms)。檢測長按的延時任務是指

    用戶從ACTION_DOWN觸發開始算起,如果500ms內沒有抬起則認為產生了長按事件,這個時候就會觸發長按事件的監聽器 onLongClickListener的onLongClick方法執行,如果該方法返回為true,則設置mHasPerformedLongPress值為true,這個值 會在ACTION_UP事件中協助判斷是否需要觸發Click事件的監聽器。如果onLongClick方法執行返回false或者是沒有產生長按事件,mHasPerformedLongPress值仍為false。

  • ACTION_MOVE:這個事件的處理相對會比較簡單,主要是判斷是否移出了當前View,如果移出則取消在ACTION_DOWN中設置的View狀態、取消長按操作檢測等。

  • ACTION_UP:執行ACTION_UP后續步驟的前提條件是當前View的狀態需要是pressed或者prepressed,即該View已經響應了ACTION_DOWN事件。然后通過判斷mHasPerformedLongPress來決定是否對Click事件進行響應,如果mHasPerformedLongPress為true表示該View已經響應了長按事件,那么就不會再對Click事件進行處理;反之則表示需要處理Click事件,處理方法就是調用Click事件監聽器onClickListener的onClick方法。最后清除View的狀態,刷新頁面完成該事件的處理。

View的onTouchEvent流程圖 .png
總結
  • 我們在屏幕上簡單的一個觸摸操作就會產生Touch事件、LongClick事件、Click事件。
  • 當我們在屏幕上觸摸時,第一個產生的事件就是Touch事件,。Android系統為Touch事件提供了兩種處理機制-基于監聽器方式(onTouchListener的onTouch處理方法)和基于回調的方式(onTouchEvent),其中優先處理監聽器方式。
  • 當ACTION_DOWN持續500ms以上,會產生LongClick事件。Android系統為LongClick事件提供了監聽器方式(onLongClickLIstener的onLongClick處理方法)。
  • 當ACTION_UP隨ACTION_DOWN而來,就會產生Click事件。Android系統為Click事件也是提供了監聽器方式(onClickListener的onClick處理方法)。但是Click事件的監聽器是否觸發則取決于該View是否消費了LongClick事件,如果未消費,那么則觸發當前View 的Click事件監聽器;如果已經消費了LongClick,則不會觸發Click事件監聽器。

下一篇將總結ViewGroup Touch事件處理流程及如何結合Robolectric在單元測試中測試Touch相關的回調方法、監聽器。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,698評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,202評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,742評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,580評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,297評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,688評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,693評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,875評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,438評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,183評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,384評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,931評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,612評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,022評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,297評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,093評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,330評論 2 377

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,702評論 25 708
  • afinalAfinal是一個android的ioc,orm框架 https://github.com/yangf...
    passiontim閱讀 15,469評論 2 45
  • 上一篇文章我們主要介紹了Android UI事件處理機制-基于監聽器方式、基于回調方法,同時從View的角度分析了...
    桃子媽咪閱讀 931評論 1 13
  • 我見過這世間最好的愛情。 沒有驚天動地、轟轟烈烈,沒有纏綿悱惻、風花雪月。 1 他是家中獨子,她卻終生不孕。他們廝...
    英語老師青城閱讀 492評論 3 2
  • 第四輪打卡:第55天 2017、4.2 覺察日記 事實:草長鶯飛四月天,岸邊楊柳女綠芽冒。清明長假的第一天比上班還...
    甌姐姐閱讀 258評論 0 0