1. 程式人生 > >Android事件分發機制詳解

Android事件分發機制詳解

概述

  眾所周知,Android事件分發機制是Android知識體系中的重點也是難點。說白了,要學好Android,事件分發機制是無論如何也繞不過去的。

  也許你會問,Android事件分發機制那麼重要,我怎麼沒用過呢?   當你被不同item的側滑刪除衝突問題所困擾時就會後悔沒有真正理解事件分發機制。   當你的針對多層級的ViewGroup做相應的觸控事件的處理的時候,如何進行事件分發的截斷,也會使你幡然醒悟,下定決心學好事件分發機制

網上關於事件分發機制的講解有很多,大體研究下來自認為存在下述不足之處。

1.單純的理論描述,沒有相應的流程圖示,讓讀者很難靜下心來研讀。 2.有流程圖示,但是沒有緊緊結合和圖示進行講解,耦合性較低,不便於理解。

3.有些文章上來直接就原始碼分析,使得在閱讀過程中更加晦澀難懂,尤其是初學者。 4.有些文章敘述的很詳細,相對冗餘,沒有突出重點。這樣就容易造成兩級分化,那些對Android事件分發機制有一定了解的同學可能看完之後,感覺不錯,受益匪淺。而對於一些對事件分發機制不太熟悉的同學,感覺看完之後更加懵逼了。

  毫不隱瞞的說,樓主在事件分發機制的學習過程中,也經歷了以下比較難熬的過程。 在這裡插入圖片描述 以為看懂了—>懵逼---->茅塞頓開---->再次懵逼---->這下理解應該正確了---->我槽,這說不通啊---->哦,原來是這樣。

  也正是基於此,我想記錄下在Android 事件分發機制中遇到的各種坑坑。方便老鐵門更快更高效的學習Android事件分發機制。  

預備知識

  稍微瞭解過Android事件分發機制的同學,Android事件分發機制主要圍繞Acticity–>ViewGroup—>View這三層來展開。因此在正式開始研究事件分發機制之前,希望大家能對Activity和View等的層級關係的知識點有個基本認知(當然了,這只是個建議,如果你不看的話,也不怎麼影響本篇文章的閱讀)。  

進入正文

先從網上盜張圖: 說明: 網上給出的示意圖呢,大都大同小異。因此給大家找了一個相對比較全面的的示意圖。

在這裡插入圖片描述  下面對照上述示意圖,大家先梳理一下事件分發機制的流程。

1 , 事件分發:public boolean dispatchTouchEvent(MotionEvent ev)

  Touch事件發生時Activity的dispatchTouchEvent(MotionEvent ev)方法會將事件傳遞給最外層View(Activity的Touch事件事實上是呼叫它內部的ViewGroup的Touch事件,可以直接當成ViewGroup處理。)的dispatchTouchEvent(MotionEvent ev)方法,該方法對事件進行分發。分發邏輯按照View的角色如下:

如果return true,事件會由當前View的dispatchTouchEvent方法進行消費,同時事件會停止向下傳遞;

如果return false,事件分發分為兩種情況:

    (1).如果當前 View 獲取的事件直接來自 Activity,則會將事件返回給Activity的onTouchEvent進行消費;
    (2).如果當前 View 獲取的事件來自外層父控制元件,則會將事件返回給父View的onTouchEvent進行消費。

如果return super.dispatchTouchEvent(ev),事件分發分為兩種情況:

 (1).如果當前View是ViewGroup,則事件會分發給onInterceptTouchEvent方法進行處理;
 (2).如果當前View是普通View,則事件直接交給onTouchEvent方法進行處理

2, 事件攔截:public boolean onInterceptTouchEvent(MotionEvent ev)

  此方法只有ViewGroup才有, Activity與普通View沒有。 上面已經提到,如果當前ViewGroup的dispatchTouchEvent(事件分發)返回super.dispatchTouchEvent(ev), 那麼事件會傳遞到傳遞到onInterceptTouchEvent方法, 該方法對事件進行攔截。攔截邏輯如下:

如果return true,則表示攔截該事件,並將事件交給當前View的onTouchEvent方法;

如果return false,則表示不攔截該事件,並將該事件交由子View的dispatchTouchEvent方法進行事件分發,重複上述過程

如果return super.onInterceptTouchEvent(ev), 事件攔截分兩種情況:

(1).如果該View(ViewGroup)存在子View且點選到了該子View, 則不攔截, 繼續分發給子View 處理, 此時相當於return false。

(2).如果該View(ViewGroup)沒有子View或者有子View但是沒有點選中子View(此時ViewGroup相當於普通View), 則交由該View的onTouchEvent響應,此時相當於return true。 

友情提示:上述中的(2)尤其重要,是對下圖中標註的紅色線路的合理解釋 如圖: 在這裡插入圖片描述

  一般的LinearLayout、 RelativeLayout、FrameLayout等ViewGroup預設不攔截, 而ScrollView、ListView等ViewGroup則可能攔截,得看具體情況。  

3, 事件響應:public boolean onTouchEvent(MotionEvent ev)

  上面已經提到,在dispatchTouchEvent(事件分發)返回super.dispatchTouchEvent(ev)並且onInterceptTouchEvent進行攔截(事件攔截返回true)的情況下,那麼事件會傳遞到onTouchEvent方法,該方法對事件進行響應。響應邏輯如下:

如果return true,則表示響應並消費該事件;

如果return fasle,則表示不響應事件,那麼該事件將會不斷向上層View的onTouchEvent方法傳遞,直到某個View的onTouchEvent方法返回true,如果到了最頂層View還是返回false,那麼認為該事件不消耗,則在同一個事件系列中,當前View無法再次接收到事 件,該事件會交由Activity的onTouchEvent進行處理;

如果return super.dispatchTouchEvent(ev),事件處理分為兩種情況:

(1).如果該View是clickable或者longclickable的,則會返回true, 表示消費了該事件, 與返回true一樣;
(2).如果該View不是clickable或者longclickable的,則會返回false, 表示不消費該事件,將會向上傳遞,與返回false一樣.

日誌分析

示意圖和理論知識講述完畢,下面帶領大家跟誰日誌簡單看下事件分發流程。 介面如下,

在這裡插入圖片描述   其對應的佈局檔案如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <com.avatarmind.toucheventdemo.CustomViewGroup
        android:id="@+id/vg_ll"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="@color/colorAccent"
        android:orientation="vertical">

        <com.avatarmind.toucheventdemo.CustomButton
            android:id="@+id/btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="按鈕" />

    </com.avatarmind.toucheventdemo.CustomViewGroup>


</LinearLayout>

    CustomViewGroup和CustomButton,顧名思義,是用了列印日誌用的。CustomViewGroup中僅僅是重寫了dispatchTouchEvent()/onInterceptTouchEvent()/onTouchEvent()方法,CustomButton也僅僅是重寫了dispatchTouchEvent()/onTouchEvent()方法而已。順便說一句,上述的重寫的方法的返回值都是預設值

return  super.xxx();

大家也可以自己通過更改dispatchTouchEvent()/onInterceptTouchEvent()/onTouchEvent()扥等的返回值,來不斷熟悉事件分發流程。

點選ViewGroupB

說明: 由於ViewGroupA是LinearLayout佈局,我沒有對其採用自定義ViewGroup的形式對其進行重寫,所以無法對其追蹤ViewGroup層面的dispatchTouchEvent()/onInterceptTouchEvent()/onTouchEvent()的列印結果。

ViewGroupB日誌資訊: 在這裡插入圖片描述

日誌解讀:(log和和上圖中完全一樣,毋庸置疑)

 activity::::-----------dispatchTouchEvent-------------down
 
 viewGroup::::::-----------dispatchTouchEvent-------------down
 viewGroup::::::-----------onInterceptTouchEvent-------------down
 viewGroup::-------------onInterceptTouchEvent----------super
 
 //由於沒有點選子View(子View沒有消費事件),所以onTouchEvent()開始回溯到ViewGroup中的onTouchEvent()
 viewGroup::::-----------onTouchEvent-------------down
 viewGroup::-------------onTouchEvent----------super
 
 //ViewGroup.dispatchTouchEvent()執行完畢(回溯)
 viewGroup::-------------dispatchTouchEvent----------super
 
 activity::::-----------onTouchEvent-------------down
 activity::::-----------onTouchEvent---------------super
 
 //activity.dispatchTouchEvent()執行完畢
 activity::::-----------dispatchTouchEvent---------------super

//由於ViewGroup/View都沒有對事件進行消費,因此Action_up事件直接交由activity.onTouchEvent()方法。前面的Action_down相當於一個探路者,誰如果要對事件進行消費,則後續的Action_up/Action_move事件才會跟過來
 activity::::-----------dispatchTouchEvent-------------up
 activity::::-----------onTouchEvent-------------up
 activity::::-----------onTouchEvent---------------super
 activity::::-----------dispatchTouchEvent---------------super
//1.ViewGroup.onInterceptTouchEvent()的預設返回return super.onInterceptTouchEvent(ev);(預設不攔截,返回值為false,事件繼續分發至子view)
//當點選了ViewGroup時,而沒有點選子View時,在沒有攔截的情況下,事件還是會向子View分發的。(由於子View沒有被點選,所以log打印不出來,無法在log中進行檢視)

View(點選Button)

日誌解讀:(大家自行分析,如果有疑問,可留言提出來)

activity::::-----------dispatchTouchEvent-------------down
viewGroup::::::-----------dispatchTouchEvent-------------down
viewGroup::::::-----------onInterceptTouchEvent-------------down
viewGroup::-------------onInterceptTouchEvent----------super
view:::-----------dispatchTouchEvent-------------down
view::::-----------onTouchEvent-------------down
view::-------------onTouchEvent----------super
view::-------------dispatchTouchEvent---------super
viewGroup::-------------dispatchTouchEvent----------super
activity::::-----------dispatchTouchEvent---------------super

activity::::-----------dispatchTouchEvent-------------move
viewGroup::::::-----------dispatchTouchEvent-------------move
viewGroup::::::-----------onInterceptTouchEvent-------------move
viewGroup::-------------onInterceptTouchEvent----------super
view:::-----------dispatchTouchEvent-------------move
view::::-----------onTouchEvent-------------move
view::-------------onTouchEvent----------super
view::-------------dispatchTouchEvent---------super
viewGroup::-------------dispatchTouchEvent----------super
activity::::-----------dispatchTouchEvent---------------super

activity::::-----------dispatchTouchEvent-------------up
viewGroup::::::-----------dispatchTouchEvent-------------up
viewGroup::::::-----------onInterceptTouchEvent-------------up
viewGroup::-------------onInterceptTouchEvent----------super
view:::-----------dispatchTouchEvent-------------up
view::::-----------onTouchEvent-------------up
view::-------------onTouchEvent----------super
view::-------------dispatchTouchEvent---------super
viewGroup::-------------dispatchTouchEvent----------super
activity::::-----------dispatchTouchEvent---------------super

順便提一嘴, 1.當我們點選ViewGroup時,如果ViewGroup中不包含子View或者包含子View但是沒有點選子View的時候,不管ViewGroup的dispatchTouchEvent()和dispatchTouchEvent()方法是否攔截,事件都不會向子View傳遞。此時的ViewGroup就是一個普通的View。

2.上面講解的都是針對ACTION_DOWN的事件傳遞,ACTION_MOVE和ACTION_UP在傳遞的過程中並不是和ACTION_DOWN 一樣,你在執行ACTION_DOWN的時候返回了false,後面一系列其它的action就不會再得到執行了。簡單的說,就是當dispatchTouchEvent在進行事件分發的時候,只有前一個事件(如ACTION_DOWN)返回true,才會收到ACTION_MOVE和ACTION_UP的事件。通俗點說ACTION_DOWN就是個偵查員,看到前面沒有敵情可以安營紮寨(被消費),就通知大部隊過來(ACTION_MOVE/ACTION_UP等等)

  好了,至此完結。不知道大家是否對Android事件分發機制流程有了一個基本的認識,如果還不是太清楚的話,給大家推薦幾篇自認為講解比較通俗易懂的文章。