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

1. 什麼是事件分發?

由於 Android 的各個 View 是層層重疊的,那麼當在以下圖的位置點擊時,這個點擊事件究竟要給誰處理呢?css

事件分發

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

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

2. 事件分發使用場景

這裏先拋出幾個問題,不知道你們有沒有遇到過滑動衝突,下面舉出三個滑動衝突場景:app

2.1 場景一

滑動衝突

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

2.2 場景二

滑動衝突2

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

2.3 場景三

滑動衝突3

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

好的,帶着這幾個問題如下來說解事件分發。測試

3. 事件分發的基本概念

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

3.1 什麼是事件?

當手指點擊屏幕的時候,就會產生事件,這些事件的信息都在 MotionEvent 這個類中,事件的種類以下表:spa

事件種類 意思
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()。

參考文章:

相關文章
相關標籤/搜索