Android 事件分发(1)—— 基本概念与流程

2,411 阅读9分钟

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()。

参考文章: