探究Android触摸事件传递机制

作者: 程序员丶星霖 | 来源:发表于2017-12-07 08:31 被阅读54次

一、什么是触摸事件?

触摸事件在Android的API中对应的是MotionEvent类,其中不同的触摸事件对应于不同的事件的类型。

1.1那什么算是触摸事件呢?

Google给出的官方解释是这样的:

Object used to report movement (mouse, pen, finger, trackball) events. Motion events may hold either absolute or relative movements and other data, depending on the type of device.

大概的意思就是(翻译的不好请多谅解):

用于报告鼠标、笔、手指、轨迹球等移动的对象的事件。运动事件可以保持有绝对或者相对运动以及其他的数据,这要取决于设备的类型。

1.2常用的事件类型

MotionEvent类中有很多事件类型,不同的触摸事件对应于不同的事件类型。常用的主要事件类型如下:

  • ACTION_DOWN:用户手指的按下操作,一个按下操作就是一次触摸事件的开始标志;
  • ACTION_MOVE:用户手指按压屏幕后,在松开之前,如果移动的距离超过一定的值,就会被判定为ACTION_MOVE操作类型;
  • ACTION_UP:用户手指离开屏幕的操作,一次抬起操作标志着一次触摸事件的结束。

每个事件对应都有自己的传递过程,从产生到传递再到停止。并且每个事件的传递载体就是整个View树。

注意:
在一次屏幕触摸事件中,ACTION_DOWN和ACTION_UP是必需的,而ACTION_MOVE则视情况而定,如果用户点击了一下屏幕,是不会触发ACTION_MOVE事件的。

二、事件传递的三个阶段

在Android系统中,拥有时间传递处理能力的类有如下三种:

  • Activity及其子类:有dispatchTouchEvent和onTouchEvent两个方法;
  • ViewGroup及其子类:有dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent三个方法;
  • View及其子类:有dispatchTouchEvent和onTouchEvent两个方法。

时间传递有如下三个阶段:

  • 分发(Dispatch):该阶段对应于dispatchTouchEvent方法,在Android系统中,所有的触摸事件都是通过该方法来进行分发的。
  • 拦截(Intercept):该阶段对应于onInterceptTouchEvent方法,该方法只在ViewGroup及其子类中才会存在,在View和Activity中是不存在的。
  • 消费(Consume):该阶段对应于onTouchEvent方法。

三、探究一下

3.1.xml布局文件

<?xml version="1.0" encoding="utf-8"?>
<com.gimiii.codinglight.MyDemoViewGroup xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    tools:context="com.gimiii.codinglight.MainActivity">

    <com.gimiii.codinglight.MyDemoTextView
        android:id="@+id/tvAdd"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView 1"
        android:padding="20dp"
        />

    <com.gimiii.codinglight.MyDemoTextView
        android:id="@+id/tvQuary"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView 2"
        android:padding="20dp"
         />

</com.gimiii.codinglight.MyDemoViewGroup>

布局文件中有一个自定义的ViewGroup和两个自定义的View。其中MainActivity中包裹着这三个View,ViewGroup中包裹着两个View。

3.2Java代码

  • MainActivity中的重写dispatchTouchEvent方法和onTouchEvent方法。
Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Log.e("log","MainActivity  return  super.diapstchTouchEvent");
    return super.dispatchTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    Log.e("log","MainActivity  return  super.onTouchEvent");
    return super.onTouchEvent(event);
}
  • 在MyDemoViewGroup中重写dispatchTouchEvent方法、onInterceptTouchEvent方法和onTouchEvent方法。
public class MyDemoViewGroup extends LinearLayout {
    public MyDemoViewGroup(Context context) {
        super(context);
    }

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

    public MyDemoViewGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("log","MyDemoViewGroup  return  super.dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e("log","MyDemoViewGroup  return  super.onInterceptTouchEvent");
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("log","MyDemoViewGroup  return  super.onTouchEvent");
        return super.onTouchEvent(event);
    }
}
  • 在MyDemoTextView中重写dispatchTouchEvent方法和onTouchEvent方法。
public class MyDemoTextView extends TextView {
    public MyDemoTextView(Context context) {
        super(context);
    }

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

    public MyDemoTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.e("log","MyDemoTextView  return  super.ondispatchTouchEvent");
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("log","MyDemoTextView  return  super.onTouchEvent");
        return super.onTouchEvent(event);
    }

}

3.3看log日志,有木有新发现

3.3.1 默认情况下,所有的方法都是return super(父类默认实现)。当触摸最里层的MyDemoTextView是,产生ACTION_DOWN事件。

  1. log日志如下:
默认情况点击View.png
  1. 上图中可以看出,传递流程如下:
默认情况点击View的事件传递流程.png

3.3.2下面再来探究一下dispatchTouchEvent()方法

(1)将MyDemoTextView中的dispatchTouchEvent()方法设置为return false。

  1. log日志如下:


    View的dispatchTouchEvent返回false.png
  1. 上图中可以看出,传递流程如下:
View中的dispatchTouchEvent方法return fasle.png

(2)将MyDemoViewGroup中的dispatchTouchEvent()方法设置为return false。

  1. log日志如下:
ViewGroup中的dispatchTouchEvent返回false.png
  1. 上图中可以看出,传递流程如下:
ViewGroup中的dispatchTouchEvent设置return false.png

(3)将MainActivity中的dispatchTouchEvent()方法设置为return false。

  1. log日志如下:


    Activity中dispatchTouchEvent返回false.png

对(1)(2)(3)的总结:
当dispatchTouchEvent返回false时,Android会将事件传递给上一级的View的onTouchEvent()方法进行处理。由于Activity没有比它更高级的View,所以如果是Activity的dispatchTouchEvent()方法return false的话,事件会直接被消费掉。

(4)将MyDemoTextView中的dispatchTouchEvent()方法设置为return true。

  1. log日志如下:


    View中的dispatchTouchEvent返回true.png
  1. 上图中可以看出,传递流程如下:
View中的dispatchTouchEvent方法return true.png

(5)将MyDemoViewGroup中的dispatchTouchEvent()方法设置为return true。

  1. log日志如下:


    ViewGroup中的dispatchTouchEvent返回true.png
  1. 上图中可以看出,传递流程如下:


    ViewGroup中的dispatchTouchEvent设置return true.png

(6)将Activity中的dispatchTouchEvent()方法设置为return true。

  1. log日志如下:


    Activity中dispatchTouchEvent返回true.png

对(4)(5)(6)的总结:
如果dispatchTouchEvent方法的返回值为true,则事件会被该View消费掉,不再向下传递。

3.3.3下面再来探究一下onTouchEvent方法

(1)将MyDemoTextView中的onTouchEvent()方法设置为return false。

  1. log日志如下:


    View 中的onTouchEvent返回false.png
  1. 上图中可以看出,传递流程如下:


    View中的onTouchEvent设置return false.png

(2)将MyDemoViewGroup中的onTouchEvent()方法设置为return false。

  1. log日志如下:


    ViewGroup中的onTouchEvent返回false.png
  1. 上图中可以看出,传递流程如下:


    ViewGroup中的onTouchEvent设置return false.png

(3)将Activity中的onTouchEvent()方法设置为return false。

  1. log日志如下:


    Activity中onTouchEvent返回false.png
  1. 上图中可以看出,传递流程如下:


    Activity中onTouchEvent设置return false.png

对(1)(2)(3)的总结:
当onTouchEvent方法返回值为false时,跟默认的情况(return super)是一样的,都是传递给上一级的onTouchEvent方法。

(4)将MyDemoTextView中的onTouchEvent()方法设置为return true。

  1. log日志如下:


    View的onTouchEvent返回true.png
  1. 上图中可以看出,传递流程如下:


    View中的onTouchEvent设置return true.png

(5)将MyDemoViewGroup中的onTouchEvent()方法设置为return true。

  1. log日志如下:


    ViewGroup的onTouchEvent返回true.png
  1. 上图中可以看出,传递流程如下:


    ViewGroup中的onTouchEvent设置return true.png

(6)将Activity中的onTouchEvent()方法设置为return true。

  1. log日志如下:


    Activity中onTouchEvent返回true.png
  1. 上图中可以看出,传递流程如下:


    Activity中onTouchEvent设置return true.png

对(4)(5)(6)的总结:
当onTouchEvent方法返回值为true时,事件会被消费掉,不再向下传递。跟dispatchTouchEvent方法返回值为true时类似。

3.3.4最后来探究一下onInterceptTouchEvent方法

(1)将MyDemoViewGroup中的onInterceptTouchEvent()方法设置为return false。

  1. log日志如下:


    ViewGroup中的onInterceptTouchEvent返回false.png
  2. 上图中可以看出,传递流程如下:


    ViewGroup中的onInterceptTouchEvent设置return false.png

总结:
onInterceptTouchEvent()方法return false和return super是一样的。都是将触摸事件传给下一级view的dispatchTouchEvent方法。

(2)将MyDemoViewGroup中的onInterceptTouchEvent()方法设置为return false。

  1. log日志如下:


    ViewGroup中的onInterceptTouchEvent返回true.png
  1. 上图中可以看出,传递流程如下:


    ViewGroup中的onInterceptTouchEvent设置return true.png

总结:
onInterceptTouchEvent()方法的返回值为true时,Touch事件会被传递给ViewGroup的onTouchEvent()方法进行处理。

4探究总结

  1. 对于dispatchTouchEvent方法的总结如下表:


    dispatchTouchEvent总结.png
  2. 对于onTouchEvent方法的总结如下表:


    onTouchEvent总结.png
  1. 对于onInterceptTouchEvent方法的总结如下表:


    onInterceptTouchEvent总结.png
  2. 触摸事件的传递总结:

  • 触摸事件的传递顺序是由Activity到ViewGroup,再由ViewGroup递归传递给它的子View;
  • ViewGroup通过onInterceptTouchEvent方法对事件进行拦截,如果该方法返回true,泽事件不会继续传递给子View,如果返回false或者super.onInterceptTouchEvent,则事件会继续传递给子View;
  • 在子View汇总对事件进行消费后,ViewGroup将接收不到任何事件。

学海无涯苦作舟

我的微信公众号.jpg

相关文章

网友评论

本文标题:探究Android触摸事件传递机制

本文链接:https://www.haomeiwen.com/subject/uqicixtx.html