天天看點

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

在自定義View中,經常需要處理Android事件分發的問題,尤其在有多個輸入裝置(如遙控、滑鼠、遊戲搖桿等)時,事件處理問題尤為突出。Android事件分發機制,一直以來都是一個讓衆多開發者困擾的難點,至少筆者在工作的前幾年中,沒有特意研究它之前,就經常雲裡霧裡。實際上,該問題的“七寸”就是dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev)這三個方法和MotionEvent事件實體,咱們這裡索性稱它們為“四大惡人”吧。本文将主要通過示例示範的方式來打這個“七寸”吧。

前言

       轉載請注明,轉自【https://www.cnblogs.com/andy-songwei/p/10998855.html】謝謝!

       在自定義View中,經常需要處理Android事件分發的問題,尤其在有多個輸入裝置(如遙控、滑鼠、遊戲搖桿等)時,事件處理問題尤為突出。Android事件分發機制,一直以來都是一個讓衆多開發者困擾的難點,至少筆者在工作的前幾年中,沒有特意研究它之前,就經常雲裡霧裡。實際上,該問題的“七寸”就是dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev)這三個方法和MotionEvent事件實體,咱們這裡索性稱它們為“四大惡人”吧。本文将主要通過示例示範的方式來打這個“七寸”吧。

        本文的主要内容如下:

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

一、事件分發機制與生活場景

       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、示範界面    

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

  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完美完成。整個事件的序列圖如下所示:

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

  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      

此時,整個事件的流程就變成了現在這樣,直覺地表現為如下的流程圖:

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

       為什麼是這種結果呢?因為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),那麼這三個方法的功能究竟是什麼呢?這裡可以看看下面的表格的總結:

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

       其實我們可以從函數名稱來大緻判斷其功能,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都不會執行。

    (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,其它均傳回預設值時

【朝花夕拾】Android自定義View篇之(五)Android事件分發機制(上)Touch三個重要方法的處理邏輯
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,其它均傳回預設值時

【朝花夕拾】Android自定義View篇之(五)Android事件分發機制(上)Touch三個重要方法的處理邏輯
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,其它均傳回預設值時

【朝花夕拾】Android自定義View篇之(五)Android事件分發機制(上)Touch三個重要方法的處理邏輯
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,其它均傳回預設值時

【朝花夕拾】Android自定義View篇之(五)Android事件分發機制(上)Touch三個重要方法的處理邏輯
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來舉例,隻是作為代表來分析,筆者完全可以通過其它的組合來分析更多的可能情況。如果分析中有不妥當或者不準确的地方,歡迎來拍磚。

繼續閱讀