Android TouchEvent 事件传递机制简单理解

Android 事件传递机制是一个常用的知识点,我们经常会遇到滑动冲突,也经常会遇到 ScrollView 或者 ListView 中嵌套 Button 时点击事件的冲突,这一切都跟事件传递机制有关,所以在看了很多资料后,发现网上访问量很多的帖子也说得不全对,我也就简单记录下对事件传递机制的理解。

参考链接(太多了,列举几个):

Android事件分发机制——Touch事件 - Jackwen - 博客园

Android 编程下 Touch 事件的分发和消费机制 - sunzn - 博客园

android的Touch事件解析(dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent)_OMG的博客-CSDN博客

Android中的dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()_xyz_lmn的博客-CSDN博客

Android 事件传递机制就是当一个触摸事件发生后,从一个窗口到另一个视图,再到另一个视图直到被消费的过程。一次完整的触摸事件是由一个 ACTION_DOWN ,若干(可以为0)个 ACTION_MOVE ,一个ACTION_UP 组成的。

事件传递机制主要涉及到三个方法:

方法名作用ActivityViewGroupView
public boolean dispatchTouchEvent(MotionEvent ev);事件分发
public boolean onInterceptTouchEvent(MotionEvent ev);事件拦截
public boolean onTouchEvent(MotionEvent ev);事件响应

可以看到,不是所有的控件都有这三个方法,只有 ViewGroup 类能够进行事件拦截。这三个方法的返回值都是 boolean 型的,也就是只有两种情况,那就好说了,看看每个方法返回 true 和 false 都是在什么情况下。

public boolean dispatchTouchEvent(MotionEvent ev);

这个方法是用来分发 MotionEvent 事件的,一般不重写。

return true:事件将分发给当前 view 并由 onTouchEvent() 方法进行消费,事件将停止向下传递。

return false:事件将返还给当前 view 的上一级(可能是 Activity ,也可能是 ViewGroup )的 onTouchEvent() 进行消费。

return super.dispatchTouchEvent(ev):事件自动分发给子 view 的 onInterceptTouchEvent() 方法。

public boolean onInterceptTouchEvent(MotionEvent ev);

这个方法是用来拦截 MotionEvent 事件的,只有 ViewGroup 类才有。

return true:事件将被拦截,并将拦截到的事件交给当前 view 的 onTouchEvent() 方法处理。

return false:事件将被放行,当前 view 上的事件将传递到子 view 上,并由子 view 的 dispatchTouchEvent() 方法来继续对事件进行分发,如果没有子 view 了,则会消费该事件,即调用 onTouchEvent() 方法。

return super.onInterceptTouchEvent(ev):默认处理事件的逻辑,与return false相同。

public boolean onTouchEvent(MotionEvent ev);

这个方法是用来响应 MotionEvent 事件的。

return true:事件将被接收并消费。

return false:事件将向上传递,由上一级的 onTouchEvent() 方法处理,并且在一次完整的触摸事件结束前将接收不到下一次事件(“记忆”功能)。

return super.onTouchEvent(ev):默认处理事件的逻辑,与return false相同。

通过三个方法的简述,可以看出 onTouchEvent()  的执行条件。下面通过一个Demo来验证一下上述返回值的对应情况。

MainActivity 中重写 dispatchTouchEvent() 和 onTouchEvent() 方法:
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i("TouchEventDemo", "TouchEventActivity | dispatchTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
        return super.dispatchTouchEvent(ev);
    }

    public boolean onTouchEvent(MotionEvent ev) {
        Log.i("TouchEventDemo", "TouchEventActivity | onTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
        return super.onTouchEvent(ev);
    }

写一个 ViewGroup 类继承 LinearLayout :
public class TouchEventFather extends LinearLayout {

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

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

    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i("TouchEventDemo", "TouchEventFather | dispatchTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
        return super.dispatchTouchEvent(ev);
    }

    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i("TouchEventDemo", "TouchEventFather | onInterceptTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
        return super.onInterceptTouchEvent(ev);
    }

    public boolean onTouchEvent(MotionEvent ev) {
        Log.i("TouchEventDemo", "TouchEventFather | onTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
        return super.onTouchEvent(ev);
    }

}

再写一个子 View 继承 TextView:
public class TouchEventChild extends TextView {

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

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

    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i("TouchEventDemo", "TouchEventChild | dispatchTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
        return super.dispatchTouchEvent(ev);
    }

    public boolean onTouchEvent(MotionEvent ev) {
        Log.i("TouchEventDemo", "TouchEventChild | onTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
        return super.onTouchEvent(ev);
    }

}

其中的 TouchEventUtil 就是将 ACTION_DOWN 等行为的值转为文字:
public class TouchEventUtil {

    public static String getTouchAction(int actionId) {
        String actionName = "";
        switch (actionId) {
            case MotionEvent.ACTION_DOWN:
                actionName = "ACTION_DOWN";
                break;
            case MotionEvent.ACTION_MOVE:
                actionName = "ACTION_MOVE";
                break;
            case MotionEvent.ACTION_UP:
                actionName = "ACTION_UP";
                break;
            case MotionEvent.ACTION_CANCEL:
                actionName = "ACTION_CANCEL";
                break;
            case MotionEvent.ACTION_OUTSIDE:
                actionName = "ACTION_OUTSIDE";
                break;
        }
        return actionName;
    }

}

activity_main.xml 布局中引入自定义的两个控件:
<?xml version="1.0" encoding="utf-8"?>
<com.qinshou.toucheventdemo.TouchEventFather xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#468AD7"
    android:gravity="center"
    android:orientation="vertical">

    <com.qinshou.toucheventdemo.TouchEventChild
        android:id="@+id/childs"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_gravity="center"
        android:background="#E1110D"
        android:gravity="center"
        android:textSize="30sp"
        android:text="Hello World!" />
</com.qinshou.toucheventdemo.TouchEventFather>
运行程序。
首先点击一下 ViewGroup 父控件,即蓝色区域,出现的打印结果如下:

现在我们 return 的都是super.XXX,可以看到发生触摸事件 ACTION_DOWN 时,先执行了 Activity 的 dispatchTouchEvent() 方法,然后是 TouchEventFather 的 dispatchTouchEvent() ,当 dispatchTouchEvent() 方法 return super.dispatchTouchEvent(ev) 时事件自动分发给子 view 的 onInterceptTouchEvent() 方法处理,于是执行了 TouchEventFather 的 onInterceptTouchEvent() 方法,当 onInterceptTouchEvent() 方法 return super.onInterceptTouchEvent(ev) 时与 return false 相同,即事件将被放行,并将事件分发给子 view ,并由子 view 继续分发,如果没有子 view 则消费该事件,当前触摸区域没有子 view 了,于是执行了 TouchEventFather 的 onTouchEvent() 方法,当 onTouchEvent() 方法 return super.onTouchEvent(ev) 时与 return false 相同,即事件将向上传递,由上一级的 onTouchEvent() 方法处理,并且在一次完整的触摸事件结束前将接收不到下一次事件(记忆“功能”),于是返回给 Activity 的 onTouchEvent() 处理,而且由于“记忆”功能,在下一次事件 ACTION_UP 发生时, Activity 并没有分发给 TouchEventFather 而是自己直接执行了 onTouchEvent() ,直到这次完整的事件结束后,下一次再发生 ACTION_DOWN 时, Activity 又会向下分发。

点击一下 View,即红色区域,出现的打印结果如下:

可以看到跟刚才一样,执行了 Activity 的 dispatchTouchEvent() 方法,然后是 TouchEventFather 的 dispatchTouchEvent() 方法,不同的是,当前触摸区域有子 view TouchEventChild,所以 TouchEventChild 又继续分发事件,于是执行了 TouchEventChild 的 dispatchTouchEvent() 方法,然后 TouchEventChild 没有子 view了,所以消费了该事件,同样, return super.onTouchEvent(ev) 时与return false相同,所以又交给父控件 TouchEventFather 执行 onTouchEvent() ,然后 TouchEventFather 也没有消费,交给 Activity 去执行 onTouchEvent() ,然后 TouchEventChild 和 TouchEventFather 直到一次完整的事件结束前,都收不到后面的的事件了。

如果我们修改一下,不是返回默认值呢,比如修改 TouchEventFather 的 onInterceptTouchEvent() 将事件拦截,返回 true,再点击红色区域,打印如下:

可以看到,由于 TouchEventFather 将事件拦截了,不会向下分发了,所以 TouchEventChild 无法接收到触摸事件了。

关于上面说的, TouchEventFather 和 TouchEventChild 只能接收到一次触摸事件,根据上面对三个方法的解释,我们将 Activity 、 TouchEventFather 、 TouchEventChild 的 onTouchEvent() 方法都返回true,是否就都能接收到事件了呢?修改代码重新运行,打印如下:

可以看到是这样的。

Android 事件传递机制采用的设计模式是责任链模式,弄懂了这个模式,也就知道它的传递原理了,这里我只是列举了几种情况,至于这三个方法是否应该重写,应该重新哪一个,应该返回 true 还是 false 还是默认值,还得具体情况具体分析,只要我们明白了原理,明白了每个方法的作用,每种返回值的不同效果,相信一般的事件冲突是不难解决的。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值