Android触摸事件传递机制

[声明] 本文来自《顾浩鑫-Android高级进阶》第一章 的学习笔记,发文目的在于传递分享知识。如涉及文章内容、版权和其它问题,请与本人联系,我将在第一时间删除内容!

前言:在Android开发中,经常会遇到触摸事件冲突,比如ViewPager的轮播图跟Fragment的划动事件冲突,或者轮播图跟下拉事件冲突,自定义view的事件处理等,本文章将会详细介绍Activity、View、ViewGroup三者的触摸事件传递机制,传递包括三个阶段:分发、拦截、消费。

一.触摸事件的类型

触摸事件对应的是 MotionEvent 类,事件类型主要有三种:

  1. ACTION_DOWN:用户按下操作,表示一次触摸事件的开始。
  2. ACTION_MOVE:在按下的情况下,进行移动。轻微的移动都会传递到该事件。
  3. ACTION_UP:用户手指离开屏幕,表示一次触摸事件的

注 :如果用户仅仅的是点击而已,则只会执行到 ACTION_DOWN 和 ACTION_UP 两个事件,不会执行到 ACTION_MOVE 事件。所以 ACTION_DOWN 和 ACTION_UP 是事件是必须的。

二.触摸事件的传递阶段

1.分发(Dispatch)

在Android系统中所有的触摸事件都是由 dispatchTouchEvent 方法进行分发的。该方法中判断事件是被消费(return true),还是继续分发给子视图处理(return super.dispatchTouchEvent),如果当前视图是ViewGroup或者其子类,则会调用onInterceptTouchEvent 判断是否截拦。

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

2.截拦(Intercept)

事件的截拦 InterceptTouchEvent 只存在于ViewGroup及其子类,activity和View是不存在该方法。该方法判断事件是被截拦 (return true)并交给自身的 OnToucEvent 方法进行消费,还是继续传递给子视图(return super.InterceptTouchEvent 或者 return false)。

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return super.onInterceptTouchEvent(ev);
    }

3.消费(Consume)

事件的消费通过 OnTouchEvent 方法判断,是被消费(return true),还是不处理(return false)并将事件传递给父视图的 OnTouchEvent 方法进行处理。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }

所有拥有事件传递能力的类:
Activity: 拥有dispatchTouchEvent 、OnTouchEvent
ViewGroup: 拥有dispatchTouchEvent 、OnInterceptTouchEvent 、OnTouchEvent
View:拥有dispatchTouchEvent 、OnTouchEvent

三、View的事件传递机制

3.1 dome

虽然说ViewGroup是View的子类,但是这是说的View指的是除ViewGroup之外的View控件子类,首先定义一个MyTextView继承TextView,打印每次事件的触发以变了解事件传递的流程。

MyTextView 类

public class MyTextView extends TextView {

    private String tag = "MyTextView";

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

    public MyTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_UP:
                Log.i(tag, "dispatchTouchEvent ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "dispatchTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "dispatchTouchEvent ACTION_DOWN");
                break;
        }
        return super.dispatchTouchEvent(event);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_UP:
                Log.i(tag, "onTouchEvent ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "onTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "onTouchEvent ACTION_DOWN");
                break;
        }
        return super.onTouchEvent(event);
    }
}

定义一个MainActivity来展现这个MyTextView,同时设置点击(onClick)和触摸(onTouch)监听。
MainActivity 类

public class MainActivity extends AppCompatActivity implements View.OnClickListener,View.OnTouchListener{
    private MyTextView mMyTextView;
    private String tag = "MainActiviy";

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

        mMyTextView = findViewById(R.id.text_view);
        //  点击监听
        mMyTextView.setOnClickListener(this);
        //  触碰监听
        mMyTextView.setOnTouchListener(this);
    }


    // MyTextView 点击事件
    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.text_view:
                Log.i(tag, "MyTextView onClick");
                break;
        }
    }

    // MyTextView 触碰事件
    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        switch (motionEvent.getAction()){
            case MotionEvent.ACTION_UP:
                Log.i(tag, "MyTextView onTouch ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "MyTextView onTouch ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "MyTextView onTouch ACTION_DOWN");
                break;
        }
        return false;
    }

    // Activity 的事件分发
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_UP:
                Log.i(tag, "dispatchTouchEvent ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "dispatchTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "dispatchTouchEvent ACTION_DOWN");
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    // Activity 的事件消费
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_UP:
                Log.i(tag, "onTouchEvent ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "onTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "onTouchEvent ACTION_DOWN");
                break;
        }
        return super.onTouchEvent(event);
    }
}

3.2 打印日志

运行后,点击Text View反馈的打印日志

03-28 08:05:14.824 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: dispatchTouchEvent ACTION_DOWN
03-28 08:05:14.824 1219-1219/com.mvp.chenzhesheng.androidadvance I/MyTextView: dispatchTouchEvent ACTION_DOWN
03-28 08:05:14.824 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onTouch ACTION_DOWN
03-28 08:05:14.824 1219-1219/com.mvp.chenzhesheng.androidadvance I/MyTextView: onTouchEvent ACTION_DOWN
03-28 08:05:15.034 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: dispatchTouchEvent ACTION_UP
03-28 08:05:15.034 1219-1219/com.mvp.chenzhesheng.androidadvance I/MyTextView: dispatchTouchEvent ACTION_UP
03-28 08:05:15.034 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onTouch ACTION_UP
03-28 08:05:15.034 1219-1219/com.mvp.chenzhesheng.androidadvance I/MyTextView: onTouchEvent ACTION_UP
03-28 08:05:15.044 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onClick

dispatchTouchEvent 、 OnTouchEvent 这两个方法的返回值存在三种情况:

  1. 直接返回true。
  2. 直接返回false。
  3. 返回父类同名方法,super.dispatchTouchEvent 或者 super.OnTouchEvent。

由于拥有不同的返回值,所以事件传递流程也有不同,经过不断修改返回值测试,最终得到了点击事件的流程图,ACTION_DOWN 和 ACTION_UP 事件的传递流程是相同的。

3.3 事件传递流程图

image

从上面的流程图可以得出结论:

  1. 触摸事件是从 dispatchTouchEvent 开始的,默认返回父类同名方法 super ,事件将会依照嵌套层次从外向内传递( MainActivity 到 MyTextView ),到达最内层的 View 时,将由 View 的 OnTouchEvent 方法处理,该方法返回 true 时进行消费不再传递,返回 false 时再由内向外传递,由外层的 OnTouchEvent 处理。
  2. 如果外层向内层传递过程中,人为干扰返回 true 消费,则不会继续继续像内部传递。
  3. View 的事件控制顺序先执行 onTouch 再执行 onClick ,如果 onTouch 返回 true 消费,则不会继续传递,也不会执行 onClick 方法。

四、ViewGroup的事件传递机制

4.1 dome

ViewGroup 是 View 的控件容器存在,拥有 dispatchTouchEvent 、 onInterceptTouchEvent 和 onTouchEvent 三个方法,比 View 多了一个 onInterceptTouchEvent 方法。为了更好的观察,我们需要自定义 MyRelativeLayout 继承 RelativeLayout 。

MyRelativeLayout类

public class MyRelativeLayout extends RelativeLayout {

    private final static String tag = "MyRelativeLayout";

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

    public MyRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_UP:
                Log.i(tag, "dispatchTouchEvent ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "dispatchTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "dispatchTouchEvent ACTION_DOWN");
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_UP:
                Log.i(tag, "onInterceptTouchEvent ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "onInterceptTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "onInterceptTouchEvent ACTION_DOWN");
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_UP:
                Log.i(tag, "onTouchEvent ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "onTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "onTouchEvent ACTION_DOWN");
                break;
        }
        return super.onTouchEvent(event);
    }
}

main_activity.xml 文件

<?xml version="1.0" encoding="utf-8"?>
<com.mvp.chenzhesheng.androidadvance.MyRelativeLayout
    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"
    >

    <com.mvp.chenzhesheng.androidadvance.MyTextView
        android:id="@+id/text_view"
        android:clickable="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"

</com.mvp.chenzhesheng.androidadvance.MyRelativeLayout>

4.2 打印日志

04-02 08:47:57.980 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: dispatchTouchEvent ACTION_DOWN
04-02 08:47:58.000 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyRelativeLayout: dispatchTouchEvent ACTION_DOWN
04-02 08:47:58.000 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyRelativeLayout: onInterceptTouchEvent ACTION_DOWN
04-02 08:47:58.000 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyTextView: dispatchTouchEvent ACTION_DOWN
04-02 08:47:58.010 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onTouch ACTION_DOWN
04-02 08:47:58.010 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyTextView: onTouchEvent ACTION_DOWN
04-02 08:47:58.200 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: dispatchTouchEvent ACTION_UP
04-02 08:47:58.200 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyRelativeLayout: dispatchTouchEvent ACTION_UP
04-02 08:47:58.200 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyRelativeLayout: onInterceptTouchEvent ACTION_UP
04-02 08:47:58.200 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyTextView: dispatchTouchEvent ACTION_UP
04-02 08:47:58.210 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onTouch ACTION_UP
04-02 08:47:58.210 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyTextView: onTouchEvent ACTION_UP
04-02 08:47:58.260 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onClick

可以看到 MainActivity 和 MyTextView 的事件传递处理中添加了一层 MyRelativeLayout 。通过不同返回值测试,得到一套流程图。

4.3 流程图

image

从上面的流程图可以得出结论:

  1. 触摸事件传递是从 Activity 传递到 ViewGroup ,再传递到 View 。如果中间没有 ViewGroup 则直接从 Activity 传递到 View 。
  2. ViewGroup 通过 onInterceptTouchEvent 方法对事件进行截拦,如果返回 false 或者 super.onInterceptTouchEvent ,则事件会继续传递给子 View 。
  3. 子 View 中对事件进行消费后,ViewGroup 将不会接收到任何事件。

五.总结

  1. 事件分发是由外到内,从 Activity 到具体的子 View ;
  2. 事件处理消费是由内到外,从子 View 到最外层 Activity ;
  3. 事件拦截只存在于 ViewGroup 中;
  4. 掌握事件传递机制可以更好的进行事件处理,无论是自定义 View 还是阅读 Framework 层源码都需要对事件传递进行学习,才能更精致的开发应用。
微信图片_20180530164801.jpg
最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容