前言
在自定義View中,經常需要處理Android事件分發的問題,尤其在有多個輸入裝置(如遙控、滑鼠、遊戲手柄等)時,事件處理問題尤為突出。Android事件分發機制,一直以來都是一個讓眾多開發者困擾的難點,至少筆者在工作的前幾年中,沒有特意研究它之前,就經常雲裡霧裡。實際上,該問題的“七寸”就是dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev)這三個方法和MotionEvent事件實體,我們們這裡索性稱它們為“四大惡人”吧。本文將主要通過示例演示的方式來打這個“七寸”吧。
本文的主要內容如下:
一、事件分發機制與生活場景
Android的事件分發機制和生活中的很多場景有著相似之處,可能Android的很多設計靈感就是來源於生活吧。
1、示例中的四個角色
在眾多示例當中,有一個經常被拿來舉例的經典場景就是PM(專案經理)、Team Leader、Programmer之間工作安排的問題,我們們這裡也用這個場景來類比,另外再加一個Boss的角色,以便於理解。現在這個公司中四個角色職位從高到低依次為,Boss > PM > Team Leader > Programmer。
2、可能出現的場景
一般來說,一個尋常的工作流程是:Boss想到有個功能很流行,就會指示PM去辦,PM會分發給Team Leader,而Team Leader會安排Proggrammer去程式設計實現。Boss是決策者,事件來源於他;PM和Team Leader是管理者,負責一層層把任務派發給自己的下屬;Programmer是具體來做開發的,所以事件最後落在他的身上了,最後由他來完成。但是現實工作中,工作並不總是這個流程,還有很多其它場景,比如:
(1)市面上出現了一很火的行業,做智慧手機。本公司是否需要也涉足這個行業,需要Boss自己開董事會來做決策。那麼這個事情就是Boss應該處理的事情,他就不會派發給PM,Boss以下的員工看來,就跟沒有任何事情一樣。
(2)Boss確定了智慧手機是一個很有前景的行業,確定了要做,於是就召集PM,做好立項工作。那這個立項工作就是這個PM的工作了,如何立項,需要招聘什麼樣人等各項準備工作,PM就得自己做好整個計劃,事情就到他這裡為止了,不會再往下派發。
(3)做手機掙了錢,Boss決定獎勵一些表現優異的Programmer。這需要先安排PM,PM然後安排下面的Team Leader對下面的Programmer們做綜合考量,將優秀者報上去。這就是Team Leader需要完成的工作,他也無法再傳下去。
(4)如前面說到的,Boss要求做一個市面上很流行的功能,經過PM和Team Leader層層派發到了Programmer手上。雖然事情派發到了Programmer手上,但也有兩種情形:
1)在Programmer能力範圍內,做得很完美。這種情況事情就在這裡被處理掉了,無需再傳遞出去了。後續Boss一系列的類似功能也會繼續派發下來,也以這樣的流程來走下去。
2)Programmer能力有限,做不了。這種情況下,他就需要告訴Team Leader這一情況,把這個任務再依次傳遞給自己的領導。這樣又有兩種情況:①Team Leader 或者PM本身也是研發出身,開發能力也很強,就把這個開發任務給完成了,這樣事情也就到此為止不再傳遞了。在Boss看來,下面的團隊有能力開發好這類需求,於是後續Boss一系列的類似功能也會繼續派發下來。由於上一次的任務中,Team Leader或者PM知道自己的手下處理不了這類任務,所以當上一級把任務分發到自己這一層的時候,就不會再繼續往下分發,而是自己親自處完成。②Team Leader和PM都是管理出身,這個功能他們也不會做。於是就層層往上報,最後到老闆那裡,老闆自己處理,是不了了之還是再找招人,或者自己也是個大牛程式設計師自己可以開發出來,這個決策由老闆來做。老闆知道自己下面的團隊完成不了這一系列任務,後續一系列這類功能,就不再派發下去了。
......
上述列出了一些比較有代表性的可能情況,下面我們們根據這些情況,來理解事件的分發機制。其實這裡PM和Team Leader可以整體作為一個角色來看,只是為了後面看程式碼和日誌方便對應,才分開為兩個角色的。
二、MotionEvent簡介
在講Android事件分發機制前,先簡單瞭解一些MotionEvent,因為它就是這個“事件”。以下擷取了部分原始碼中的描述:
1 ...... 2 * <p> 3 * Motion events describe movements in terms of an action code and a set of axis values. 4 * The action code specifies the state change that occurred such as a pointer going 5 * down or up. The axis values describe the position and other movement properties. 6 * </p> 7 ...... 8 public final class MotionEvent extends InputEvent implements Parcelable { 9 public static final int ACTION_DOWN = 0; 10 public static final int ACTION_UP = 1; 11 public static final int ACTION_MOVE = 2; 12 ...... 13 }
MotionEvent,顧名思義,動作事件的意思。它通過一個action碼和一套座標值來描述動作。action碼指定了當如指標按下或者抬起等事件發生時的狀態改變,座標值則描述了事件在螢幕中的位置和其它動作屬性值。如下內容為MotionEvent的toString方法列印出來的結果:
MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=173.0, y[0]=138.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x2, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=34268429, downTime=34268429, deviceId=8, source=0x1002 }
從這裡可以看到,事件發生的action,時間,座標等很多資訊。本文中暫時只關注aciton這個欄位,“action=ACTION_DOWN”表示了按下事件。
平時觸控螢幕時,一個最簡單的事件包括了“ACTION_DOWN”和“ACTION_UP”,“ACTION_DOWN”表示手指按下,而““ACTION_UP”表示手指抬起來,這兩個action才構成了一個完整的事件。如果手指在螢幕上有移動,還會包含“ACTION_MOVE”,此時一個完整的事件就包括“ACTION_DOWN”,多個“ACTION_MOVE”,“ACTION_UP”。當然,實際工作中會有很多複雜的情況出現,可能會出現一些其它的aciton,本文為了演示的方便,只考慮“ACTION_DOWN”和“ACTION_UP”的場景。
三、示例程式碼
為了演示事件分發機制的工作流程,這裡編寫一個示例來進行演示。整個Acitivity模擬Boss角色;在其介面中的最外層模擬PM,繼承自RelativeLayout,是一個父佈局;PM下巢狀一層,也是一個父佈局,繼承自RelativeLayout,用於模擬Team Leader;最裡面一層是一個葉子View,繼承自Button,模擬Programmer。效果圖及對應程式碼分別如下。
1、演示介面
2、預設場景下的程式碼示例
如下的程式碼中,需要重寫的方法dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev)均返回預設值,即super.xxx。平時我們們使用系統原生控制元件時,無法修改它們的原始碼,所以系統給的預設場景就是這樣的。
(1)Boss:EventDemoActivity
1 public class EventDemoActivity extends AppCompatActivity { 2 3 @Override 4 protected void onCreate(Bundle savedInstanceState) { 5 super.onCreate(savedInstanceState); 6 setContentView(R.layout.activity_event_demo); 7 } 8 9 @Override 10 public boolean dispatchTouchEvent(MotionEvent ev) { 11 Log.i("songzheweiwang","[EventDemoActivity-->dispatchTouchEvent]ev="+EventUtil.parseAction(ev.getAction())); 12 return super.dispatchTouchEvent(ev); 13 } 14 15 @Override 16 public boolean onTouchEvent(MotionEvent event) { 17 Log.i("songzheweiwang","[EventDemoActivity-->onTouchEvent]event="+EventUtil.parseAction(event.getAction())); 18 return super.onTouchEvent(event); 19 } 20 }
該Activity的佈局檔案為
1 //==========================activity_event_demo.xml=========================== 2 <?xml version="1.0" encoding="utf-8"?> 3 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 4 android:layout_width="match_parent" 5 android:layout_height="match_parent"> 6 7 <com.example.demos.customviewdemo.ViewGroupOuter 8 android:layout_width="300dp" 9 android:layout_height="300dp" 10 android:layout_centerInParent="true" 11 android:background="@android:color/holo_orange_dark"> 12 13 <com.example.demos.customviewdemo.ViewGroupMiddle 14 android:layout_width="200dp" 15 android:layout_height="200dp" 16 android:layout_centerInParent="true" 17 android:background="@android:color/holo_blue_dark"> 18 19 <com.example.demos.customviewdemo.ViewInner 20 android:id="@+id/viewInner" 21 android:layout_width="100dp" 22 android:layout_height="100dp" 23 android:layout_centerInParent="true" 24 android:background="@android:color/holo_green_dark"/> 25 </com.example.demos.customviewdemo.ViewGroupMiddle> 26 </com.example.demos.customviewdemo.ViewGroupOuter> 27 </RelativeLayout>
(2)PM:ViewGroupOuter
1 public class ViewGroupOuter extends RelativeLayout { 2 3 public ViewGroupOuter(Context context, @Nullable AttributeSet attrs) { 4 super(context, attrs); 5 } 6 7 @Override 8 public boolean dispatchTouchEvent(MotionEvent ev) { 9 Log.i("songzheweiwang","[ViewGroupOuter-->dispatchTouchEvent]ev="+EventUtil.parseAction(ev.getAction())); 10 return super.dispatchTouchEvent(ev); 11 } 12 13 @Override 14 public boolean onInterceptTouchEvent(MotionEvent ev) { 15 Log.i("songzheweiwang","[ViewGroupOuter-->onInterceptTouchEvent]ev="+EventUtil.parseAction(ev.getAction())); 16 return super.onInterceptTouchEvent(ev); 17 } 18 19 @Override 20 public boolean onTouchEvent(MotionEvent event) { 21 Log.i("songzheweiwang","[ViewGroupOuter-->onTouchEvent]event="+EventUtil.parseAction(event.getAction())); 22 return super.onTouchEvent(event); 23 } 24 }
(3)Team Leader:ViewGroupMiddle
1 public class ViewGroupMiddle extends RelativeLayout { 2 3 public ViewGroupMiddle(Context context, @Nullable AttributeSet attrs) { 4 super(context, attrs); 5 } 6 7 @Override 8 public boolean dispatchTouchEvent(MotionEvent ev) { 9 Log.i("songzheweiwang","[ViewGroupMiddle-->dispatchTouchEvent]ev="+EventUtil.parseAction(ev.getAction())); 10 return super.dispatchTouchEvent(ev); 11 } 12 13 @Override 14 public boolean onInterceptTouchEvent(MotionEvent ev) { 15 Log.i("songzheweiwang","[ViewGroupMiddle-->onInterceptTouchEvent]ev="+EventUtil.parseAction(ev.getAction())); 16 return super.onInterceptTouchEvent(ev); 17 } 18 19 @Override 20 public boolean onTouchEvent(MotionEvent event) { 21 Log.i("songzheweiwang","[ViewGroupMiddle-->onTouchEvent]event="+EventUtil.parseAction(event.getAction())); 22 return super.onTouchEvent(event); 23 } 24 }
(4)Programmer:ViewInner
這裡先以Button為例,因為Button預設是可以處理Touch事件的,也就是說,事件傳到這裡時,能被完美地處理掉。
1 @SuppressLint("AppCompatCustomView") 2 public class ViewInner extends Button{ 3 4 public ViewInner(Context context, AttributeSet attrs) { 5 super(context, attrs); 6 } 7 8 @Override 9 public boolean dispatchTouchEvent(MotionEvent event) { 10 Log.i("songzheweiwang","[ViewInner-->dispatchTouchEvent]event="+EventUtil.parseAction(event.getAction())); 11 return super.dispatchTouchEvent(event); 12 } 13 14 @Override 15 public boolean onTouchEvent(MotionEvent event) { 16 Log.i("songzheweiwang","[ViewInner-->onTouchEvent]event="+EventUtil.parseAction(event.getAction())); 17 return super.onTouchEvent(event); 18 } 19 }
(5)輔助類:
1 public class EventUtil { 2 public static String parseAction(int action) { 3 String actionName = "Unknow:action=" + action; 4 switch (action) { 5 case MotionEvent.ACTION_DOWN: 6 actionName = "ACTION_DOWN"; 7 break; 8 case MotionEvent.ACTION_MOVE: 9 actionName = "ACTION_MOVE"; 10 break; 11 case MotionEvent.ACTION_UP: 12 actionName = "ACTION_UP"; 13 break; 14 default: 15 break; 16 } 17 return actionName; 18 } 19 }
3、日誌
點選上圖中不同的區域,會有不同的結果。這裡點選最中間的View,點選其他區域的結果及分析,我們在後面再介紹。
1 06-07 13:35:23.483 18298-18298/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN 2 06-07 13:35:23.483 18298-18298/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN 3 06-07 13:35:23.483 18298-18298/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN 4 06-07 13:35:23.483 18298-18298/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN 5 06-07 13:35:23.483 18298-18298/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onInterceptTouchEvent]ev=ACTION_DOWN 6 06-07 13:35:23.483 18298-18298/com.example.demos I/songzheweiwang: [ViewInner-->dispatchTouchEvent]event=ACTION_DOWN 7 06-07 13:35:23.483 18298-18298/com.example.demos I/songzheweiwang: [ViewInner-->onTouchEvent]event=ACTION_DOWN 8 06-07 13:35:23.524 18298-18298/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP 9 06-07 13:35:23.525 18298-18298/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_UP 10 06-07 13:35:23.525 18298-18298/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_UP 11 06-07 13:35:23.525 18298-18298/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_UP 12 06-07 13:35:23.525 18298-18298/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onInterceptTouchEvent]ev=ACTION_UP 13 06-07 13:35:23.525 18298-18298/com.example.demos I/songzheweiwang: [ViewInner-->dispatchTouchEvent]event=ACTION_UP 14 06-07 13:35:23.525 18298-18298/com.example.demos I/songzheweiwang: [ViewInner-->onTouchEvent]event=ACTION_UP
4、結果分析
該事件包含了兩action:ACTION_DOWN和ACTION_UP。前我們說過,Programmer完美處理好了事件,本次流程就到這裡為止了,不再傳遞,Boss認為團隊有能力處理這類任務,所以類似的任務也會同樣會交給手下的團隊,所以ACTION_UP也走了類似的流程,那麼整個事件就算完成了,由Programmer完美完成。整個事件的序列圖如下所示:
5、ViewInner沒有能力處理的情況
上面的例子中,ViewInner是一個Button,它預設是有能力處理這次Touch事件的。但是如果這是一個預設沒有能力處理該時間的控制元件,又會是一種怎樣的情形呢?我們們把ViewInner改為繼承View再看看結果(仍然點選中間的ViewInner)。
1 06-07 15:04:25.815 19652-19652/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN 2 06-07 15:04:25.815 19652-19652/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN 3 06-07 15:04:25.815 19652-19652/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN 4 06-07 15:04:25.815 19652-19652/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN 5 06-07 15:04:25.815 19652-19652/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onInterceptTouchEvent]ev=ACTION_DOWN 6 06-07 15:04:25.815 19652-19652/com.example.demos I/songzheweiwang: [ViewInner-->dispatchTouchEvent]event=ACTION_DOWN 7 06-07 15:04:25.815 19652-19652/com.example.demos I/songzheweiwang: [ViewInner-->onTouchEvent]event=ACTION_DOWN 8 06-07 15:04:25.816 19652-19652/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onTouchEvent]event=ACTION_DOWN 9 06-07 15:04:25.816 19652-19652/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onTouchEvent]event=ACTION_DOWN 10 06-07 15:04:25.816 19652-19652/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_DOWN
11 06-07 15:04:25.865 19652-19652/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP 12 06-07 15:04:25.865 19652-19652/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_UP
此時,整個事件的流程就變成了現在這樣,直觀地表現為如下的流程圖:
為什麼是這種結果呢?因為ViewInner繼承自View,其預設情況下,是沒有處理Touch事件的能力的。所以Programmer處理不了了,就一層一層網上報告,由於ViewGroupMiddle和ViewGroupOuter都繼承自RelativeLayout,預設也是沒有處理Touch事件的能力的,所以最後ACTION_DOWN事件就回到了Boss這裡,由Boss自己來處理。Boss發現自己首先的團隊無法處理這類事件,所以後面的ACTION_UP事件就自己處理了,而沒有再往下派發了。這一點,和第二節中的第(4)點的第2)小點的情況②的場景是一致的。
如果在activity_event_demo.xml中為各個控制元件(包括父佈局)加上屬性[android:clickable="true"],或者在Activity中為對應控制元件新增監聽點選事件,那麼這個控制元件就有了處理Touch事件的能力了,就和之前使用Button的場景一樣的。讀者還可以試試在ViewInnner為View時,其父佈局有處理Touch事件的能力時的場景(注意,要點選ViewInner來測試),那麼這就是和第二節中的第(4)點的第2)小點的情況①的場景是一致的,這裡我們們不再分析日誌畫流程圖了。
四、Touch事件主要方法說明
1、Touch的三個主要方法概述
前面一直提到Touch事件的3個主要方法:dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent ev),那麼這三個方法的功能究竟是什麼呢?這裡可以看看下面的表格的總結:
其實我們可以從函式名稱來大致判斷其功能,dispatchTouchEvent,分發觸控事件,就是把事件傳遞下去,準確來說就是是否要傳遞到子View以及自己的onInterceptTouchEvent方法和onTouchEvent方法,也就是說,不僅管子Viiew,還管自身剩下的兩個回撥方法。onInterceptTouchEvent,事件攔截,它只管自身子View,而不會影響到自身後面兩個方法的執行,如果攔截了,可以記憶為讓自己的手下們無事可做。這兩個方法容易混淆,需要重點理解和記憶。
在上述表格中還可以看到,Activity是無法回撥onIntercepTouchEvent方法的,因為這個方法是ViewGroup中的方法,而Activity也不是View體系中,不是檢視類,所以沒有這個方法。我們可以這樣記憶,Activity是Boss,不是打工行列中的一員,自己的任務就是讓下面的打工者沒去做事情,所有該方法對他來說,沒有意義。葉子View也沒有這個方法,因為自己沒有子View了,也沒有攔截的意義。
由於這三個方法都是boolean值,再加上預設情形下會返回super.xxx,這樣,每一個方法都會有三種可選值。我們們這裡先了解一下每一種取值會產生怎麼樣的結果。
2、事件分發:public boolean dispatchTouchEvent(MotionEvent ev)
Touch事件發生時,Activity的dispatchTouchEvent方法會將事件傳遞給最外層控制元件的dispatchTouchEvent方法,並由該控制元件進行分發下去。從根元素依次往下傳遞,一直到最裡面的葉子View,或者中途被某個控制元件終止,才結束這個派發過程。其分發邏輯如下:
(1)如果 return true,事件會分發到當前控制元件的dispatchTouchEvent方法中處理。同時事件停止往下分發,且當前控制元件的onInterceptTouchEvent和onTouchEvent都不會執行。(這裡筆者也不清楚事件是被處理了,還是不了了之了。從列印的log上看,和被處理掉的情形很相似,本次事件,如ACTION_DOWN,到此為止,然後Activity繼續給出後續事件,如ACTION_UP,繼續走到當前控制元件的這個方法中,直到這一系列事件全部走完。但是從實驗結果看,如果在acitivty中給該控制元件新增了點選事件,發現點選後沒有響應,說明這個事件沒有被處理。所以筆者很納悶,暫時還沒有找到權威的說法。這裡暫時按照其它眾多資料中說法,被當前的dispatchTouchEvent方法給處理了。)
(2)如果 return false,事件停止往下派發,且當前控制元件的onInterceptTouchEvent和onTouchEvent也都不會執行。同時將事件返回給上一級的onTouchEvent事件,由上一級去決定處理還是繼續往上傳遞,自己不處理。
(3)返回預設的super.dispatchTouchEvent,事件會自動分發給當前View的onInterceptTouchEvent。
3、事件攔截:public boolean onInterceptTouchEvent(MotionEvent ev)
在當前控制元件的dispatchTouchEvent方法返回預設的方式時,其攔截邏輯如下:
1)return true,表示將事件進行攔截,並將攔截到的事件交給當前控制元件的onTouchEvent來處理。
2)return false,表示將事件放行,事件會被傳遞到子View上,並由子View的dispatchTouchEvent方法繼續派發。
3)return super.onInterceptTouchEvent(ev),和返回false的邏輯一樣。
4、事件響應:public boolean onTouchEvent(MotionEvent ev)
(1)該方法會被執行的情形有如下兩種:
1)子View沒有處理事件,將事件返回來;
2)當前控制元件中dispatchTouchEvent返回預設的super.dispatchTouchEvent的情況下,且該控制元件的onInterceptTouchEvent返回false或者預設的super.onInterceptTouchEvent時。
(2)onTouchEvent方法響應邏輯如下:
1)返回true,當前事件會被處理掉。
2)返回false,當前事件不會被處理,返回給上一級的onTouchEvent方法來處理。
3)返回super.onTouchEvent,如果自己有能力處理該事件,則會處理,此時super.onTouchEvent的值為true;否則,如果自己沒有能力處理該事件,則將事件返回到上一級中的onTouchEvent方法中處理,當前super.onTouchEvent的值為false。
五、Touch的3個主要方法返回值對事件分發影響的案例分析
上一節中介紹了Touch的3個主要方法的返回值下,對事件分發的處理邏輯。本節中,我們們通過修改前面這三個方法中的返回值,來驗證事件的分發流程(注意:以下情況下均點選中間的ViewInner控制元件)。
1、ViewGroupMiddle中dispatchTouchEvent返回true,其它均返回預設值時
1 06-07 19:15:53.220 25298-25298/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN 2 06-07 19:15:53.221 25298-25298/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN 3 06-07 19:15:53.221 25298-25298/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN 4 06-07 19:15:53.222 25298-25298/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN
5 06-07 19:15:53.237 25298-25298/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP 6 06-07 19:15:53.237 25298-25298/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_UP 7 06-07 19:15:53.237 25298-25298/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_UP 8 06-07 19:15:53.238 25298-25298/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_UP
參照第四節中的結論,ViewGroupMiddle的dispatchTouchEvent返回true,事件從Acitivty,經過ViewGroupOuter分發到ViewGroupMiddle中,在其dispatchTouchEvent方法中處理。ViewGroupMiddle的onInterceptTouchEvent和onTouchEvent均不會被呼叫,且事件也不會再往ViewInner中傳遞。既然事件是在ViewGroupMiddle的dispatchTouchEvent中被處理了,在Boss EventDemoActivity看來,自己手下的團隊有能力處理這類事件,所以ACTION_UP也被派發下來,走同樣的流程,直到所有事件處理完畢。
2、ViewGroupMiddle中dispatchTouchEvent返回false,其它均返回預設值時
1 06-07 19:31:50.093 25668-25668/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN 2 06-07 19:31:50.094 25668-25668/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN 3 06-07 19:31:50.094 25668-25668/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN 4 06-07 19:31:50.094 25668-25668/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN 5 06-07 19:31:50.094 25668-25668/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onTouchEvent]event=ACTION_DOWN 6 06-07 19:31:50.094 25668-25668/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_DOWN 7 06-07 19:31:50.151 25668-25668/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP 8 06-07 19:31:50.151 25668-25668/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_UP
參照第四節中的結論,ViewGroupMiddle的dispatchTouchEvent返回true,事件從Acitivty,經過ViewGroupOuter分發到ViewGroupMiddle中,且在dispatchTouchEvent方法中不處理此事件。ViewGroupMiddle的onInterceptTouchEvent和onTouchEvent均不會被呼叫,且事件也不會再往ViewInner中傳遞。自己處理不了事件,傳遞給上一級的onTouchEvent來處理,上一級也沒能力處理,最後傳給了EventDemoActivity的onTouchEvent。此時,在Boss看來,自己手下團隊處理不了這類事件,所以後面的事件就不再傳遞下去,都有自己來處理。
3、ViewGroupMiddle中onInterceptTouchEvent返回true,其它均返回預設值時
1 06-07 19:41:08.894 26055-26055/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN 2 06-07 19:41:08.894 26055-26055/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN 3 06-07 19:41:08.894 26055-26055/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN 4 06-07 19:41:08.894 26055-26055/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN 5 06-07 19:41:08.894 26055-26055/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onInterceptTouchEvent]ev=ACTION_DOWN 6 06-07 19:41:08.894 26055-26055/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onTouchEvent]event=ACTION_DOWN 7 06-07 19:41:08.894 26055-26055/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onTouchEvent]event=ACTION_DOWN 8 06-07 19:41:08.895 26055-26055/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_DOWN 9 06-07 19:41:08.900 26055-26055/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP 10 06-07 19:41:08.901 26055-26055/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_UP
事件在ViewGroupMiddle中被攔截了,事件不再派發到ViewInner中,而是交給自己的onTouchEvent來處理。前面說過,ViewGroupMiddle繼承自RelativeLayout,預設是沒有能力處理Touch事件的,於是就傳遞到上一級的onTouchEvent中,直到EventDemoActivity中的onTouchEvent方法。此時,在Boss看來,自己手下團隊處理不了這類事件,所以後面的事件就不再傳遞下去,都有自己來處理。
4、ViewGroupMiddle中onInterceptTouchEvent返回false,其它均返回預設值時
1 06-07 19:48:58.130 26400-26400/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN 2 06-07 19:48:58.130 26400-26400/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN 3 06-07 19:48:58.130 26400-26400/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN 4 06-07 19:48:58.130 26400-26400/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN 5 06-07 19:48:58.130 26400-26400/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onInterceptTouchEvent]ev=ACTION_DOWN 6 06-07 19:48:58.130 26400-26400/com.example.demos I/songzheweiwang: [ViewInner-->dispatchTouchEvent]event=ACTION_DOWN 7 06-07 19:48:58.131 26400-26400/com.example.demos I/songzheweiwang: [ViewInner-->onTouchEvent]event=ACTION_DOWN 8 06-07 19:48:58.131 26400-26400/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onTouchEvent]event=ACTION_DOWN 9 06-07 19:48:58.131 26400-26400/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onTouchEvent]event=ACTION_DOWN 10 06-07 19:48:58.131 26400-26400/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_DOWN 11 06-07 19:48:58.162 26400-26400/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP 12 06-07 19:48:58.162 26400-26400/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_UP
這種情況下,和使用預設super.onInterceptTouchEvent時是一樣的,Log中中的日誌也驗證了這一點。事件派發流程在第三節中詳細講解過,這裡就不再贅述了。
5、ViewGroupMiddle中onTouchEvent為true,其它均返回預設值時
1 06-07 19:53:51.516 26711-26711/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN 2 06-07 19:53:51.517 26711-26711/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN 3 06-07 19:53:51.517 26711-26711/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN 4 06-07 19:53:51.517 26711-26711/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN 5 06-07 19:53:51.517 26711-26711/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onInterceptTouchEvent]ev=ACTION_DOWN 6 06-07 19:53:51.517 26711-26711/com.example.demos I/songzheweiwang: [ViewInner-->dispatchTouchEvent]event=ACTION_DOWN 7 06-07 19:53:51.517 26711-26711/com.example.demos I/songzheweiwang: [ViewInner-->onTouchEvent]event=ACTION_DOWN 8 06-07 19:53:51.517 26711-26711/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onTouchEvent]event=ACTION_DOWN 9 06-07 19:53:51.582 26711-26711/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP 10 06-07 19:53:51.583 26711-26711/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_UP 11 06-07 19:53:51.583 26711-26711/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_UP 12 06-07 19:53:51.583 26711-26711/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_UP 13 06-07 19:53:51.583 26711-26711/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onTouchEvent]event=ACTION_UP
事件依次傳遞到ViewInner的onTouchEvent方法中,ViewInner預設沒有能力處理該事件,傳遞到上一級ViewGroupMiddle中的onTouchEvent來處理。返回true表示被處理了,本次事件在此中止了。在Boss看來,手下團隊有能力處理這類事件,所以後面的ACTION_UP事件仍然往下分發了。這裡需要注意的是,ACTION_UP在ViewGroupMiddle的dispatchTouchEvent執行後直接進入到其onTouchEvent方法中了,沒有經過onInterceptTouchEvent方法走,也沒有往ViewInner中分發。這個場景就好像,通過ACTION_DOWN,ViewGroupMiddle已經知道自己的手下ViewInner處理不了這類任務,所以當同類任務從上面領導發放到自己這裡的時候,就不用再繼續往下分發,而是直接直接就處理掉了。
6、ViewGroupMiddle中onTouchEvent為false,其它均返回預設值時
1 06-07 20:09:49.746 27357-27357/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN 2 06-07 20:09:49.746 27357-27357/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN 3 06-07 20:09:49.746 27357-27357/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN 4 06-07 20:09:49.746 27357-27357/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN 5 06-07 20:09:49.746 27357-27357/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onInterceptTouchEvent]ev=ACTION_DOWN 6 06-07 20:09:49.746 27357-27357/com.example.demos I/songzheweiwang: [ViewInner-->dispatchTouchEvent]event=ACTION_DOWN 7 06-07 20:09:49.746 27357-27357/com.example.demos I/songzheweiwang: [ViewInner-->onTouchEvent]event=ACTION_DOWN 8 06-07 20:09:49.746 27357-27357/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onTouchEvent]event=ACTION_DOWN 9 06-07 20:09:49.747 27357-27357/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onTouchEvent]event=ACTION_DOWN 10 06-07 20:09:49.747 27357-27357/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_DOWN 11 06-07 20:09:49.803 27357-27357/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP 12 06-07 20:09:49.803 27357-27357/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_UP
這裡在activity_event_demo.xml中使用ViewGroupMiddle時新增[android:clickable="true"],將ViewGroupMiddle設定為預設可以處理Touch事件。當設定為false值時,從日誌來看,表明ViewGroupMiddle無論是否有能力,都確實沒有處理事件,而是傳給了上級。
7, 均為預設值時
當ViewGroupMiddle中onTouchEvent返回預設的super.onTouchEvent時,我們在第三節中分析過ViewInner有能處理和沒有能力處理兩種情況下的事件處理邏輯,這裡筆者不再贅述。現在還有一個結論需要讀者驗證,就是都在返回預設super.xxx情況下,可以在ViewGroupMiddle中onTouchEvent方法中列印出super.onTouchEvent的值。可以發現,如果ViewGroupMiddle中onTouchEvent方法可以處理事件,則值為true,如果沒有處理Touch事件的能力,則會返回false。這一點在第四節中講過。
六、當觸控其它區域時分析
在前面分析列印log結果的時候,筆者都著重強調了要點選正中心的ViewInner。這是因為點選不同的區域,會產生不同的邏輯處理結果。那麼點選區域和事件分發結果有什麼樣的關係呢?下面將第三節中的例子,3個主要方法都返回預設的super.xxx方法,由外到內依次點選Boss,PM,Team Leader,Programmer四個區域。得到了如下的log資訊:
1 06-07 20:27:44.390 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN 2 06-07 20:27:44.391 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_DOWN 3 06-07 20:27:44.405 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP 4 06-07 20:27:44.405 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_UP 5 6 06-07 20:27:48.298 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN 7 06-07 20:27:48.299 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN 8 06-07 20:27:48.299 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN 9 06-07 20:27:48.299 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onTouchEvent]event=ACTION_DOWN 10 06-07 20:27:48.299 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_DOWN 11 06-07 20:27:48.338 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP 12 06-07 20:27:48.339 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_UP 13 14 06-07 20:27:52.681 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN 15 06-07 20:27:52.681 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN 16 06-07 20:27:52.681 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN 17 06-07 20:27:52.681 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN 18 06-07 20:27:52.681 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onInterceptTouchEvent]ev=ACTION_DOWN 19 06-07 20:27:52.682 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onTouchEvent]event=ACTION_DOWN 20 06-07 20:27:52.682 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onTouchEvent]event=ACTION_DOWN 21 06-07 20:27:52.682 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_DOWN 22 06-07 20:27:52.749 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP 23 06-07 20:27:52.749 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_UP 24 25 06-07 20:27:57.448 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_DOWN 26 06-07 20:27:57.449 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupOuter-->dispatchTouchEvent]ev=ACTION_DOWN 27 06-07 20:27:57.449 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onInterceptTouchEvent]ev=ACTION_DOWN 28 06-07 20:27:57.449 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN 29 06-07 20:27:57.449 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onInterceptTouchEvent]ev=ACTION_DOWN 30 06-07 20:27:57.449 28523-28523/com.example.demos I/songzheweiwang: [ViewInner-->dispatchTouchEvent]event=ACTION_DOWN 31 06-07 20:27:57.449 28523-28523/com.example.demos I/songzheweiwang: [ViewInner-->onTouchEvent]event=ACTION_DOWN 32 06-07 20:27:57.449 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupMiddle-->onTouchEvent]event=ACTION_DOWN 33 06-07 20:27:57.449 28523-28523/com.example.demos I/songzheweiwang: [ViewGroupOuter-->onTouchEvent]event=ACTION_DOWN 34 06-07 20:27:57.450 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_DOWN 35 06-07 20:27:57.514 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->dispatchTouchEvent]ev=ACTION_UP 36 06-07 20:27:57.515 28523-28523/com.example.demos I/songzheweiwang: [EventDemoActivity-->onTouchEvent]event=ACTION_UP
這四次觸控事件的日誌結果用空格隔開,分析該log可以發現:當點選Boss區域時,裡面的三個控制元件均未觸發事件;當點選PM區域時,Team Leader和Programmer中的沒有任何動作;點選Team Leader區域時,只有Programmer沒有觸發任何事件;當點選Programmer區域時,4個角色均被觸發。那麼這個結論就很顯而易見了:當點選到View系統的某一層時,事件從外往內傳遞時,只到被點選的那一層為止,不會再派發到其子View中。這4個場景,是不是和我們開篇第一節中提到的4種場景很相似呢?點選到哪個區域,說明原本安排的任務本身就應該由該職位的人來完成,其手下就完全可以當成是不存在的。
結語
到目前為止,Android的事件分發和傳遞機制就分析完了。本文中Touch事件的3個主要方法返回值均有3種情形,所以會有多種邏輯處理組合。這裡選取了中間層ViewGroupMiddle來舉例,只是作為代表來分析,筆者完全可以通過其它的組合來分析更多的可能情況。如果分析中有不妥當或者不準確的地方,歡迎來拍磚。