Android 事件分發(1)—— 基本概念與流程

ZedeChan發表於2018-08-09

1. 什麼是事件分發?

因為 Android 的各個 View 是層層重疊的,那麼當在如下圖的位置點選時,這個點選事件究竟要給誰處理呢?

事件分發

這個時候就需要事件分發機制來處理了。

說白了,事件分發其實就是決定將點選事件分發給誰處理的一套規則。

2. 事件分發使用場景

這裡先丟擲幾個問題,不知道大家有沒有遇到過滑動衝突,下面舉出三個滑動衝突場景:

2.1 場景一

滑動衝突

圖中有兩個 View,外部的 View 是橫向滑動的,而內部的 View 是豎向滑動的。這個時候在內部的 View 進行滑動,怎麼對這個事件進行分發呢?

2.2 場景二

滑動衝突2

現在外部的 View 和內部的 View 的滑動方向是一樣的,這個時候在內部的 View 滑動,這時候怎麼解決滑動衝突呢?

2.3 場景三

滑動衝突3

如果是前面兩個場景同時混合,這時候又怎麼分發呢?

好的,帶著這幾個問題以下來講解事件分發。

3. 事件分發的基本概念

在學習事件分發必須要理解幾個關鍵的概念。

3.1 什麼是事件?

當手指點選螢幕的時候,就會產生事件,這些事件的資訊都在 MotionEvent 這個類中,事件的種類如下表:

事件種類 意思
MotionEvent.ACTION_DOWN 手指按下 View
MotionEvent.ACTION_MOVE 手指在 View 滑動
MotionEvent.ACTION_UP 手指抬起

3.2 什麼是事件序列?

一個事件序列就是從手指按下 View 開始直到手指離開 View 產生的一系列事件。

其實這裡的意思就是以 DOWN 事件開始,中間產生無數個 MOVE 事件,最後以 UP 事件結束。

3.3 事件序列的傳遞順序

Activity -> ViewGroup -> ... -> View

3.4 事件序列關鍵方法

方法 作用
dispathchTouchEvent() 分發點選事件
onInterceptTouchEvent() 判斷是否攔截點選事件
onTouchEvent() 處理點選事件

4. 事件分發的關鍵概念

4.1 事件分發流程

要注意的是 Activity 和 View 是沒有 onInterceptTouchEvent() 方法的。

現在探究一下事件分發的流程是怎麼樣的。

探究的過程就使用一步步列印,並且改變相關的返回值,然後畫出區域性的流程圖,最後得出全域性的流程圖。

這裡先看看測試程式碼:

Util:

public class Util {

    public static String getAction(MotionEvent event) {
        String action = "";

        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            action = "ACTION_DOWN";
        } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
            action = "ACTION_MOVE";
        } else if (event.getAction() == MotionEvent.ACTION_UP) {
            action = "ACTION_UP";
        }

        return action;
    }
}
複製程式碼

這個類的作用只是讓列印可以列印出來具體是哪個點選事件。

MainActivity:

public class MainActivity extends AppCompatActivity {

    public String TAG = "chan";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d(TAG, "=================Activity dispatchTouchEvent Action: "
                + Util.getAction(ev));
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        Log.d(TAG, "=================Activity onTouchEvent Action: "
                + Util.getAction(ev));
        return super.onTouchEvent(ev);
    }
}
複製程式碼

ViewGroup1:

public class ViewGroup1 extends LinearLayout {

    public String TAG = "chan";

    public ViewGroup1(Context context) {
        super(context);
    }

    public ViewGroup1(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d(TAG, "=================ViewGroup dispatchTouchEvent Action: "
                + Util.getAction(ev));
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d(TAG, "=================ViewGroup onInterceptTouchEvent Action: "
                + Util.getAction(ev));
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "=================ViewGroup onTouchEvent Action: "
                + Util.getAction(event));
        return super.onTouchEvent(event);
    }
}
複製程式碼

View1:

public class View1 extends View  {

    public String TAG = "chan";


    public View1(Context context) {
        super(context);
    }

    public View1(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d(TAG, "=================View dispatchTouchEvent Action: "
                + Util.getAction(event));
        return super.dispatchTouchEvent(event);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean result = super.onTouchEvent(event);
        Log.d(TAG, "=================View onTouchEvent Action: "
                + Util.getAction(event));
        return super.onTouchEvent(event);
    }

}
複製程式碼

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <com.example.administrator.toucheventdemo.ViewGroup1
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="#00ff00">


        <com.example.administrator.toucheventdemo.View1
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:background="#ff0000"/>

    </com.example.administrator.toucheventdemo.ViewGroup1>


</android.support.constraint.ConstraintLayout>
複製程式碼

4.1.1 改變 Activity 的 dispathchTouchEvent() 方法的返回值

這裡返回值會有三種可能性:

  • super.dispatchTouchEvent(ev)
  • true
  • false

4.1.1.1 返回 super.dispatchTouchEvent(ev)

列印結果:

08-09 09:49:03.167 26886-26886/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_DOWN
08-09 09:49:03.168 26886-26886/com.example.administrator.toucheventdemo D/chan: =================ViewGroup dispatchTouchEvent Action: ACTION_DOWN
    =================ViewGroup onInterceptTouchEvent Action: ACTION_DOWN
    =================View dispatchTouchEvent Action: ACTION_DOWN
08-09 09:49:03.169 26886-26886/com.example.administrator.toucheventdemo D/chan: =================View onTouchEvent Action: ACTION_DOWN
    =================ViewGroup onTouchEvent Action: ACTION_DOWN
    =================Activity onTouchEvent Action: ACTION_DOWN
08-09 09:49:03.190 26886-26886/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
    =================Activity onTouchEvent Action: ACTION_MOVE
08-09 09:49:03.196 26886-26886/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
    =================Activity onTouchEvent Action: ACTION_MOVE
08-09 09:49:03.197 26886-26886/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_UP
08-09 09:49:03.197 26886-26886/com.example.administrator.toucheventdemo D/chan: =================Activity onTouchEvent Action: ACTION_UP
複製程式碼

根據列印結果畫出的流程圖:

事件分發流程圖1

上面的流程圖只是畫了 DOWN 事件的分發過程,從列印結果可以看出後面的 MOVE 和 UP 事件,Activity 並沒有分發下去,而是自己處理了。這裡可以得出一個結論:

如果一個 View 不消費 DOWN 事件,那麼同一個事件序列剩下的事件將不會再交給它處理,而會交給它的父元素處理。

4.1.1.2 返回 true

列印結果:

08-09 10:11:19.533 28137-28137/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_DOWN
08-09 10:11:19.546 28137-28137/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
08-09 10:11:19.569 28137-28137/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
08-09 10:11:19.572 28137-28137/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_UP
複製程式碼

如果直接返回 true,不會分發任何事件,可以在 Activity 處理事件。

4.1.1.3 返回 false

列印結果:

08-09 10:13:54.394 28415-28415/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_DOWN
08-09 10:13:54.442 28415-28415/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
08-09 10:13:54.444 28415-28415/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_UP
複製程式碼

如果直接返回 false,不會分發任何事件,可以在 Activity 處理事件。

從這裡就可以看出,Acitity 如果要將事件分發出去,必須在 dispatchTouchEvent() 返回預設值。

4.1.2 改變 ViewGroup 的 dispathchTouchEvent() 方法的返回值

前面已經探究過 ViewGroup 返回預設值的情況了,現在只看看返回 true 和 false 的情況。

4.1.2.1 返回 true

列印結果:

08-09 10:20:51.658 29295-29295/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_DOWN
08-09 10:20:51.659 29295-29295/com.example.administrator.toucheventdemo D/chan: =================ViewGroup dispatchTouchEvent Action: ACTION_DOWN
08-09 10:20:51.690 29295-29295/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
08-09 10:20:51.691 29295-29295/com.example.administrator.toucheventdemo D/chan: =================ViewGroup dispatchTouchEvent Action: ACTION_MOVE
08-09 10:20:51.699 29295-29295/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
    =================ViewGroup dispatchTouchEvent Action: ACTION_MOVE
08-09 10:20:51.702 29295-29295/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_UP
    =================ViewGroup dispatchTouchEvent Action: ACTION_UP
複製程式碼

根據列印結果畫出的流程圖:

事件分發流程圖2

4.1.2.1 返回 false

列印結果:

08-09 10:25:27.046 29672-29672/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_DOWN
08-09 10:25:27.047 29672-29672/com.example.administrator.toucheventdemo D/chan: =================ViewGroup dispatchTouchEvent Action: ACTION_DOWN
08-09 10:25:27.048 29672-29672/com.example.administrator.toucheventdemo D/chan: =================Activity onTouchEvent Action: ACTION_DOWN
08-09 10:25:27.062 29672-29672/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
    =================Activity onTouchEvent Action: ACTION_MOVE
08-09 10:25:27.078 29672-29672/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
    =================Activity onTouchEvent Action: ACTION_MOVE
08-09 10:25:27.088 29672-29672/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
    =================Activity onTouchEvent Action: ACTION_MOVE
08-09 10:25:27.091 29672-29672/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_UP
    =================Activity onTouchEvent Action: ACTION_UP
複製程式碼

事件分發流程圖3

從結果可以看出,如果 ViewGroup 不消費 DOWN 事件,事件序列接下來的事件都不會再交給它處理。

4.1.3 改變 ViewGroup 的 onInterceptTouchEvent() 方法的返回值

這裡要說明一下的是 onInterceptTouchEvent() 預設返回值是 false,所以這裡只探究返回 true 和 預設值就可以了。

4.1.3.1 返回 true

列印結果:

08-09 10:31:52.499 30168-30168/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_DOWN
08-09 10:31:52.501 30168-30168/com.example.administrator.toucheventdemo D/chan: =================ViewGroup dispatchTouchEvent Action: ACTION_DOWN
    =================ViewGroup onInterceptTouchEvent Action: ACTION_DOWN
    =================ViewGroup onTouchEvent Action: ACTION_DOWN
    =================Activity onTouchEvent Action: ACTION_DOWN
08-09 10:31:52.515 30168-30168/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_UP
    =================Activity onTouchEvent Action: ACTION_UP
複製程式碼

流程圖:

事件分發流程圖 4

上面的列印結果是 ViewGroup 的 onTouchEvent 返回預設值的情況,但是上面的流程圖可以知道,如果 onTouchEvent 返回預設值或者 false,代表不消耗 DOWN 事件,那麼事件序列的其他事件就不會再給到它了。

4.1.3.2 返回 false 或者 預設值

如果是這種情況,就會直接將事件分發給下一個 View,上面已經說過了,這裡就不再贅述了。

到這裡其實 View 的分發事件與 ViewGroup 是基本相似的,View 的 dispathchTouchEvent() 返回 true 就代表消費該事件,返回 false 就代表不處理事件,返回預設值就將事件傳遞給 onTouchEvent()。

從以上結果就可以得到總的流程圖:

事件分發流程圖5

4.2 事件分發的一些結論

其實從上面的探究可以得出一些結論:

  1. 如果一個 View 不消費 DOWN 事件,那麼同一個事件序列剩下的事件將不會再交給它處理,而會交給它的父元素處理
  2. 如果一個 ViewGroup 決定攔截事件(onInterceptTouchEvent 返回 true),那麼 onInterceptTouchEvent() 就不會再被呼叫

這裡還要驗證一個結論,就是如果 View 不消費除了 DOWN 事件的其他事件,那麼這些事件就會直接交給 Activity 的 onTouchEvent() 處理,而不會再交給它的父容器的 onTouchEvent()。

這裡修改下 View1 的程式碼:

public class View1 extends View  {

    public String TAG = "chan";


    public View1(Context context) {
        super(context);
    }

    public View1(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d(TAG, "=================View dispatchTouchEvent Action: "
                + Util.getAction(event));
        return super.dispatchTouchEvent(event);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean result = super.onTouchEvent(event);
        Log.d(TAG, "=================View onTouchEvent Action: "
                + Util.getAction(event));
        if(event.getAction() != MotionEvent.ACTION_DOWN) {
            return false;
        }
        return true;
    }

}
複製程式碼

可以看到程式碼我只是修改了 onTouchEvent() 的程式碼,這裡只消費 DOWN 事件,其餘事件都不消費。

列印結果:

08-09 11:18:52.846 3098-3098/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_DOWN
08-09 11:18:52.847 3098-3098/com.example.administrator.toucheventdemo D/chan: =================ViewGroup dispatchTouchEvent Action: ACTION_DOWN
08-09 11:18:52.848 3098-3098/com.example.administrator.toucheventdemo D/chan: =================ViewGroup onInterceptTouchEvent Action: ACTION_DOWN
    =================View dispatchTouchEvent Action: ACTION_DOWN
    =================View onTouchEvent Action: ACTION_DOWN
08-09 11:18:52.863 3098-3098/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
08-09 11:18:52.864 3098-3098/com.example.administrator.toucheventdemo D/chan: =================ViewGroup dispatchTouchEvent Action: ACTION_MOVE
    =================ViewGroup onInterceptTouchEvent Action: ACTION_MOVE
    =================View dispatchTouchEvent Action: ACTION_MOVE
    =================View onTouchEvent Action: ACTION_MOVE
    =================Activity onTouchEvent Action: ACTION_MOVE
08-09 11:18:52.877 3098-3098/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_UP
    =================ViewGroup dispatchTouchEvent Action: ACTION_UP
    =================ViewGroup onInterceptTouchEvent Action: ACTION_UP
    =================View dispatchTouchEvent Action: ACTION_UP
08-09 11:18:52.878 3098-3098/com.example.administrator.toucheventdemo D/chan: =================View onTouchEvent Action: ACTION_UP
    =================Activity onTouchEvent Action: ACTION_UP
複製程式碼

從列印結果可以看出,除了 DOWN 事件外,其餘事件就直接交給 Activity 處理,並沒有再回撥 ViewGroup 的 onTouchEvent()。

參考文章:

相關文章