【朝花夕拾】Android自定義View篇之(五)Android事件分發機制(上)三個重要方法的處理邏輯

宋者為王發表於2019-06-10

前言

       在自定義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來舉例,只是作為代表來分析,筆者完全可以通過其它的組合來分析更多的可能情況。如果分析中有不妥當或者不準確的地方,歡迎來拍磚。

 

相關文章