Android View 事件傳遞機制剖析

crazy261發表於2015-07-28

View事件傳遞 touch事件分發

學習Android一年有餘,今天開始以自己的理解去介紹一下Android開發常用到的基礎技術。第一個介紹的是View 的事件傳遞機制,建議首先去看一下[codeKK中關於View事件傳遞介紹的文章][1],我這裡只是對這個的補充以及自己理解。有不正確的地方歡迎拍 磚☺。

第一部分:知識儲備

  1. 在Android中所有的TouchEvent事件都封裝在MotionEvent物件中,而TouchEvent事件主要包括Touch的位置,時間,歷史記錄,滑動等等。
  2. 常見MotionEvent的型別包括:ACTION_DOWN,ACTION_MOVE,ACTION_CANCEL,ACTION_UP,ACTION_SCROLL等等。
  3. 我們關心的對事件的處理實質上就是 dispatchTouchEvent,onTouchEvent,onInterceptTouchEvent這三個函式,分別代表事件傳遞,事件消費 和事件攔截的意思,這三個函式都是boolean型別的,如果返回值為true代表該事件被消耗。

    (註明:以上的內容是從codeKK文章中部分摘取的)

第二部分:實戰演練

以我的理解,要掌握整個事件的傳遞過程,必須得理清楚那三個事件處理函式,最好是自己寫寫程式碼驗證一下。 以下我根據事件的消耗分四個部分:不攔截不消費,不攔截消費,攔截不消費,攔截消費。為了更好的理解,假設佈局層次如下:

Android View 事件傳遞機制

這個是在UI Automator中的截圖,佈局很簡單,最外層為一個LinearLayout,然後巢狀了兩個LinearLayout。在這個裡為了方便我簡稱為從最外到裡分別為L0,L1,L2。其預覽圖如下:

Android View 事件傳遞機制

佈局檔案如下:

<?xml version="1.0" encoding="utf-8"?>
<com.example.TouchEventDemo.MyLinearLayout0
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:id="@+id/L0"
        android:background="@android:color/white"
        >
    <com.example.TouchEventDemo.MyLinearLayout1
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/L1"
            android:layout_margin="20dp"
            android:background="@android:color/holo_blue_dark"
            >
        <com.example.TouchEventDemo.MyLinearLayout2
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:id="@+id/L2"
                android:background="@android:color/holo_red_light"
                android:layout_margin="20dp"
                android:orientation="horizontal"
                />
    </com.example.TouchEventDemo.MyLinearLayout1>
</com.example.TouchEventDemo.MyLinearLayout0>

這裡為了更好的看清楚事件的傳遞,我們需要自定義三個LinearLayout,並重寫三個事件函式,dispatchTouchEvent,onTouchEvent,onInterceptTouchEvent

   @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(TAG, "MyLinearLayout0------------dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(TAG, "MyLinearLayout0------------onTouchEvent");
        return super.onTouchEvent(event);
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i(TAG, "MyLinearLayout0------------onInterceptTouchEvent");
        return super.onInterceptTouchEvent(ev);
    }

好了,前期準備工作已經完成,我們開始驗證:

1.不攔截不消費:

即所有的onTouchEvent,onInterceptTouchEvent函式返回為fasle。預期的流程應該是:

Android View 事件傳遞機制

當我們點選L2即紅色區域程式執行的結果是:

Android View 事件傳遞機制

可以看到和我們預期的一模一樣。

2.不攔截消費:

這裡假定L1對事件不攔截消費,即L1.onInterceptTouchEvent位false,L1.onTouchEvent返回為true,說明L1對消費了事件,且沒有攔截事件。同樣我們的預期流程應該有兩種情況: 第一種:L2消費掉事件,即L2.onTouchEvent返回為true,其流程應該是:

Android View 事件傳遞機制

當我們點選L2即紅色區域程式執行的結果是:

Android View 事件傳遞機制

第二種:L2沒有消費掉事件,即L2.onTouchEvent返回為fasle,其流程應該是:

Android View 事件傳遞機制

當我們點選L2即紅色區域程式執行的結果是:

Android View 事件傳遞機制

到這裡前兩種情況基本介紹完了,總結下,在這裡最重要的一點要記住,如果子VIEW沒有消費掉ACTION_DOWN的事件,則後續的UP/MOVE事件將不會到來。

3.攔截不消費:

這裡假定的物件還是L1,即L1對事件攔截但不消費,即L1.onInterceptTouchEvent位true,L1.onTouchEvent返回為false。從字面上可以L1攔截了事件,則L2是不會在接受到任何事件的。預期的流程應該是:

Android View 事件傳遞機制

當我們點選L2即紅色區域程式執行的結果是:

Android View 事件傳遞機制

可以看到和我們流程圖是一樣的。

4.攔截消費:

這裡假定的對還是L1,即L1對事件攔截且消費,即L1.onInterceptTouchEvent位true,L1.onTouchEvent返回為true。預期的流程應該是:

Android View 事件傳遞機制

當我們點選L2即紅色區域程式執行的結果是:

Android View 事件傳遞機制

執行結果和流程圖一樣

第三部分:總結

以上就是就是view事件傳遞的流程,當然還沒有包括點選,長按等事件。下面對全文做個總結: (1) 事件從 Activity.dispatchTouchEvent()開始傳遞,只要沒有被停止或攔截,從最上層的 View(ViewGroup)開始一直往下(子 View)傳遞。子 View 可以通過 onTouchEvent()對事件進行處理。

(2) 事件由父 View(ViewGroup)傳遞給子 View,ViewGroup 可以通過 onInterceptTouchEvent()對事件做攔截,停止其往下傳遞。

(3) 如果事件從上往下傳遞過程中一直沒有被停止,且最底層子 View 沒有消費事件,事件會反向往上傳遞,這時父 View(ViewGroup)可以進行消費,如果還是沒有被消費的話,最後會到 Activity 的 onTouchEvent()函式。

(4) 如果 View 沒有對 ACTION_DOWN 進行消費,之後的其他事件不會傳遞過來。

(5) OnTouchListener 優先於 onTouchEvent()對事件進行消費。

原始碼:連結:http://pan.baidu.com/s/1sjsUmsP 密碼:v6lp

相關文章