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

来源:互联网 发布:淘宝上卖军品违法吗 编辑:程序博客网 时间:2024/06/05 14:52

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

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

http://www.cnblogs.com/Jackwen/p/5239035.html

http://www.cnblogs.com/sunzn/archive/2013/05/10/3064129.html#!comments

http://blog.csdn.net/omg_2012/article/details/7881443

http://blog.csdn.net/xyz_lmn/article/details/12517911

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

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

方法名作用ActivityViewGroupViewpublic 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 0