完全理解android事件分發機制

許佳佳233發表於2017-01-12

前言

之前筆者其實已經寫過事件分發機制的文章:快速理解android事件傳遞攔截機制概念
但是,現在看來其實更像是一篇知識概括,多處可能未講清楚,於是打算重寫事件分發,用一篇文章大致講清楚。
首先,形式上筆者最先思考的是使用原始碼,此者能從原理上講解分發機制,比起侃侃而談好得多。但是大量的原始碼往往會讓新手產生畏懼難以理解,於是筆者最終還是打算主要使用例項log輸出來讓讀者理解android事件分發。

重要函式

筆者此次主要提及最常用的幾個函式:
(其間區別看原始碼很容易理解,此處直接給上結果)
onClick():這個函式是是View提供給我們的OnClickListener這個介面中的函式,在這裡可以自定義對點選事件的處理邏輯。會在onTouchEvent()中進行呼叫。
onTouch():這個函式是View提供給我們的OnTouchListener這個介面中的函式,在這裡面可以自定義對觸控事件的處理邏輯。
onTouchEvent():這個函式是view內部的觸控事件的處理方式,其間包括獲取焦點,呼叫onClick()等等。
dispatchTouchEvent():這個是View的事件分發函式,在ViewGroup中進行重寫。在View中其間會呼叫onTouchEvent(),在ViewGroup中其間會呼叫onInterceptTouchEvent()和onTouchEvent()。
onInterceptTouchEvent():這個函式是事件攔截函式,是ViewGroup才有的函式。

重要函式執行順序

此處我們通過一個很簡單的例子進行說明,示例:
這裡寫圖片描述
示例xml程式碼如下:

<?xml version="1.0" encoding="utf-8"?>
<com.example.double2.dispatchevent.LinearLayoutA
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_a"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/holo_blue_dark"
    android:padding="30dp"
    android:orientation="vertical"
    >

    <com.example.double2.dispatchevent.LinearLayoutB
        android:id="@+id/ll_b"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        android:padding="30dp"
        android:orientation="vertical"
        >

        <com.example.double2.dispatchevent.LinearLayoutC
            android:id="@+id/v_c"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/black"
            />
    </com.example.double2.dispatchevent.LinearLayoutB>
</com.example.double2.dispatchevent.LinearLayoutA>


由外到裡主要是三個LinearLayout,分別為A、B、C。筆者分別在五個關鍵函式中加上了Log,最終點選一下,輸出的值如下:

01-12 01:28:58.136 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_A
01-12 01:28:58.136 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_A
01-12 01:28:58.136 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_B
01-12 01:28:58.136 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_B
01-12 01:28:58.136 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_C
01-12 01:28:58.136 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_C
01-12 01:28:58.136 2442-2442/com.example.double2.dispatchevent D/XJJ: onTouch_C
01-12 01:28:58.136 2442-2442/com.example.double2.dispatchevent D/XJJ: onTouchEvent_C
01-12 01:28:58.203 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_A
01-12 01:28:58.203 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_A
01-12 01:28:58.203 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_B
01-12 01:28:58.203 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_B
01-12 01:28:58.203 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_C
01-12 01:28:58.203 2442-2442/com.example.double2.dispatchevent D/XJJ: onTouch_C
01-12 01:28:58.203 2442-2442/com.example.double2.dispatchevent D/XJJ: onTouchEvent_C
01-12 01:28:58.203 2442-2442/com.example.double2.dispatchevent D/XJJ: onClick_C

如上,我們可以看到五個函式的大致執行順序如下:

  1. dispatchTouchEvent()
  2. onInterceptTouchEvent()
  3. onTouch()
  4. onTouchEvent()
  5. onClick()

好奇的讀者肯定會問,為什麼事件分發執行了兩次呢?
其實很簡單,因為的確有兩個分發的事件,一次是“手指按下”的事件,一次是“手指抬起”的事件。我們可以看到,只有在“手指抬起”的時候,才會觸發onClick()事件。

此處為了便於大家理解,也附上一張事件分發的圖:
這裡寫圖片描述

控制事件分發

然而還有一個值得我們在意的事情,就是onTouch()以及onTouchEvent()只有在C中執行,而在B和A的就不執行了。
此處,我們就必須再講一點了。

dispatchTouchEvent()、onInterceptTouchEvent() 、onTouch()、onTouchEvent()這四個函式,返回值為false的時候,事件會繼續向下分發,一旦返回值為true,事件就不再向下分發。
而onClick()沒有返回值

根據這點我們可以知道,一定是C的onTouchEvent()中返回了true,我們將其更改後再看一下效果。
原來的程式碼為:

@Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("XJJ","onTouchEvent_C");
        return super.onTouchEvent(event);
    }

更改後:

@Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("XJJ","onTouchEvent_C");
        return false;
    }

效果:

01-12 01:24:06.250 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_A
01-12 01:24:06.250 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_A
01-12 01:24:06.250 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_B
01-12 01:24:06.250 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_B
01-12 01:24:06.250 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_C
01-12 01:24:06.250 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_C
01-12 01:24:06.250 2442-2442/com.example.double2.dispatchevent D/XJJ: onTouch_C
01-12 01:24:06.250 2442-2442/com.example.double2.dispatchevent D/XJJ: onTouchEvent_C
01-12 01:24:06.250 2442-2442/com.example.double2.dispatchevent D/XJJ: onTouch_B
01-12 01:24:06.250 2442-2442/com.example.double2.dispatchevent D/XJJ: onTouchEvent_B
01-12 01:24:06.360 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_A
01-12 01:24:06.360 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_A
01-12 01:24:06.360 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_B
01-12 01:24:06.360 2442-2442/com.example.double2.dispatchevent D/XJJ: onTouch_B
01-12 01:24:06.360 2442-2442/com.example.double2.dispatchevent D/XJJ: onTouchEvent_B
01-12 01:24:06.360 2442-2442/com.example.double2.dispatchevent D/XJJ: onClick_B

這樣操作之後,我們可以發現,“手指按下”時,onTouch()以及onTouchEvent()事件就可以傳遞到B了。但是同時,我們也可以發現,當“手指抬起”時,C的onTouch()以及onTouchEvent()事件就不會執行了。

當然,如果需要onTouch()以及onTouchEvent()事件傳遞到A,那麼只需要將B的onTouchEvent()也返回false即可。(此處就不重複演示了)

那麼如果onTouchEvent()返回值設定為true了之後,是不是onClick()事件是不是就不會執行了呢?效果如下:

01-12 01:39:21.560 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_A
01-12 01:39:21.560 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_A
01-12 01:39:21.560 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_B
01-12 01:39:21.560 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_B
01-12 01:39:21.560 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_C
01-12 01:39:21.560 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_C
01-12 01:39:21.560 2442-2442/com.example.double2.dispatchevent D/XJJ: onTouch_C
01-12 01:39:21.560 2442-2442/com.example.double2.dispatchevent D/XJJ: onTouchEvent_C
01-12 01:39:21.617 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_A
01-12 01:39:21.617 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_A
01-12 01:39:21.617 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_B
01-12 01:39:21.617 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_B
01-12 01:39:21.617 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_C
01-12 01:39:21.617 2442-2442/com.example.double2.dispatchevent D/XJJ: onTouch_C
01-12 01:39:21.617 2442-2442/com.example.double2.dispatchevent D/XJJ: onTouchEvent_C

onClick()的確是不會執行了,如此我們也嘗試一下onTouch()返回值設定為true,效果如下:

01-12 01:40:41.101 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_A
01-12 01:40:41.101 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_A
01-12 01:40:41.101 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_B
01-12 01:40:41.101 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_B
01-12 01:40:41.101 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_C
01-12 01:40:41.101 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_C
01-12 01:40:41.101 2442-2442/com.example.double2.dispatchevent D/XJJ: onTouch_C
01-12 01:40:41.173 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_A
01-12 01:40:41.173 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_A
01-12 01:40:41.173 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_B
01-12 01:40:41.173 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_B
01-12 01:40:41.173 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_C
01-12 01:40:41.173 2442-2442/com.example.double2.dispatchevent D/XJJ: onTouch_C

如此,我們可知,當onTouch()返回值設定為true的時候,onTouchEvent()的確是不會執行了。

到這裡,我們其實只剩下對onInterceptTouchEvent() 的分析了,為何沒有dispatchTouchEvent()了呢?
因為dispatchTouchEvent()是事件分發的函式,對View而言,我們阻止它內部的事件分發是沒有什麼意義的,而我們要控制ViewGroup的事件分發則是通過onInterceptTouchEvent() 來執行的。

如此我們便假設一個應用場景,A包括B,B包括C,B為橫向滑動,C為豎向滑動,當橫向滑動的加速度大於豎向滑動的加速度的時候,我們僅僅讓B響應事件,而不把事件傳遞給C。

我們可以通過onInterceptTouchEvent() 來實現,僅僅只需將B的onInterceptTouchEvent()返回值設定為true即可,效果如下:

01-12 01:50:18.513 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_A
01-12 01:50:18.513 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_A
01-12 01:50:18.513 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_B
01-12 01:50:18.513 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_B
01-12 01:50:18.513 2442-2442/com.example.double2.dispatchevent D/XJJ: onTouch_B
01-12 01:50:18.513 2442-2442/com.example.double2.dispatchevent D/XJJ: onTouchEvent_B
01-12 01:50:18.621 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_A
01-12 01:50:18.621 2442-2442/com.example.double2.dispatchevent D/XJJ: onInterceptTouchEvent_A
01-12 01:50:18.621 2442-2442/com.example.double2.dispatchevent D/XJJ: dispatchTouchEvent_B
01-12 01:50:18.621 2442-2442/com.example.double2.dispatchevent D/XJJ: onTouch_B
01-12 01:50:18.621 2442-2442/com.example.double2.dispatchevent D/XJJ: onTouchEvent_B
01-12 01:50:18.621 2442-2442/com.example.double2.dispatchevent D/XJJ: onClick_B

原始碼

最後是附上demo的原始碼,大家可以自行下載理解。
http://download.csdn.net/detail/double2hao/9735367

相關文章