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

参考文章:

相关文章
相关标签/搜索