以下内容转载自https://blog.csdn.net/yoonerloop/article/details/73834997,原作者为一杯清泉,部分内容有所添加。
事件分发机制在Android是一个比较重要的知识体系,比较复杂,往往弄得一些初学者一头雾水,在开发中充分地理解事件分发机制有助于我们解决滑动冲突问题,和一些自定义控件中的疑难问题,提高开发效率。接下里将详细介绍事件分发机制的整个过程和机理。
概述
当一个View被初始化结束后,经过一系列的处理,通过PhoneWindow把该View加载到Activity上面。当我们手指操作屏幕时候,是首先被当前的Activity捕获到的,交给Activity处理,Activity不具有事件的拦截能力,但是具有事件分发和事件响应的能力,若Activity没有子类View处理,则Activity处理;若Activity有子类View处理,则把事件分发给子类View处理。对于View处理事件则需要经过dispatchTouchEvent,onInterceptTouchEvent、onTouchEvent三个阶段,关系图如下:
Touch事件相关方法 | 方法功能 | ViewGroup | View | Activity |
public boolean dispatchTouchEvent(MotionEvent ev) | 事件分发 | Yes | Yes | Yes |
public boolean onInterceptTouchEvent(MotionEvent ev) | 事件拦截 | Yes | No | No |
public boolean onTouchEvent(MotionEvent ev) | 事件响应 | Yes | Yes | Yes |
其实用两张图就可以很清楚地描述整个事件分发机制(图源:一文读懂Android View事件分发机制)
事件分发
事件分发本身也具有消费的能力,当用户触摸屏幕时,被Activity捕获到,他把这个事件通过dispatchTouchEvent(MotionEvent ev)方法会将事件传递给最外层View的dispatchTouchEvent(MotionEvent ev)方法,该方法对事件进行分发。这里分为三种情况:
1、返回true:表示在本层不再进行分发,且已经进行消费掉,事件传递结束。如果不想让Activity的控件进行消费,可以重写Activity的dispatchTouchEvent方法,强制返回true。
2、返回false:表示在本层中不再进行事件的分发。如果当前的View的事件直接来自于Activity,则Activity的onTouchEvent进行消费;如果当前View的事件来自于父控件,则交给上层控件的onTouchEvent方法进行消费。
3、返回super.dispatchTouchEvent(ev):事件将分发给本层的事件拦截onInterceptTouchEvent 方法进行处理。
事件拦截
如上文所提到的,第三种情况会调用onInterceptTouchEvent(MotionEvent ev)方法会进行处理。分为三种情况:
1、返回true:表示将事件进行拦截,并将拦截到的事件交由本层控件 的 onTouchEvent 进行处理。
2、返回false:表示不拦截该事件,并将该事件交由子View的dispatchTouchEvent方法进行事件分发。
3、返回super.onInterceptTouchEvent(ev):默认表示不拦截该事件,并将该事件交由子View的dispatchTouchEvent方法进行事件分发。和返回false一样。
事件响应
以下三种情况下回执行onTouchEvent(MotionEvent ev)方法:
1、dispatchTouchEvent返回true
2、onInterceptTouchEvent返回true
3、onInterceptTouchEvent返回super.onInterceptTouchEvent(ev)
在回调onTouchEvent方法时候也分为三种情况:
1、返回true:表示onTouchEvent处理完事件后消费了此次事件。此时事件终结。
2、返回false:则表示不响应事件,那么该事件将会不断向上层View的onTouchEvent方法传递,直到某个View的onTouchEvent方法返回true,如果到了最顶层View还是返回false,那么认为该事件不消耗,则在同一个事件系列中,当前View无法再次接收到事件,该事件会交由Activity的onTouchEvent进行处理,如果Activity不处理,则这个事件就无响应。
3、返回super.dispatchTouchEvent(ev):表示不响应。和return false一致。
注意:
1、ViewGroup默认返回false,即不拦截任何事件。
2、不作为容器的View,如TextView,一旦接受到事件,就调用onTouchEvent方法,它们本身没有onInterceptTouchEvent方法。正常情况下,它们都会消耗事件(返回true),除非它们是不可点击的(clickable和longClickable都为false),那么就会交由父容器的onTouchEvent处理。
点击事件的流程分析
在系统为我们提供的一些空间中有的默认是可以点击的,如Button,有些是默认不可以点击的,例如:TextView。那么当我们给他设置了点击事件后他是如何执行的呢?OnTouchListener、onTouchEvent、OnClickListener、onTouch、onClick的执行顺序又是什么呢?这里分析一下。通过给view设置触摸事件,点击事件,重写onTouchEvent方法,我们进行日志的打印分析。
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e("ATG", "onTouch");
return false;
}
});
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("ATG", "onClick");
}
});
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("ATG", "onTouchEvent");
return super.onTouchEvent(event);
}
打印的日志如下:
E/ATG: onTouch
E/ATG: onTouchEvent
E/ATG: onTouch
E/ATG: onTouchEvent
E/ATG: onClick
当按下时候打印onTouch,onTouchEvent;取消的时候打印:onTouch,onTouchEvent,onClick。这里执行了两次事件:按下和取消,所以onTouch,onTouchEvent才执行了两次,可以得出:
点击事件的执行顺序是:OnTouchListener的onTouch方法→onTouchEvent→OnClickListener的onClick方法
当设置onTouch返回值为true时候,打印日志如下:
E/ATG: onTouch
E/ATG: onTouch
可以看出View的onTouchEvent,onClick方法没有被执行,这个再次论证了上面的结论。
同样的方法我们这里改变onTouchEvent的返回值,来观察日志的打印情况,这里就不再次写出过程了,有兴趣的同学可以下来试试。通过以上分析,我们得出以下结论:
结论:
1、点击事件分发过程如下 dispatchTouchEvent→OnTouchListener的onTouch方法→onTouchEvent→OnClickListener的onClick方法。所以三者优先级是onTouch→onTouchEvent→onClick。
2、平时给调用的setOnClickListener,优先级是最低的,所以onTouchEvent或OnTouchListener的onTouch方法如果返回true,则不响应onClick方法。
3、如果一个View同时监听了onTouch事件和onClick事件,则在onTouch里面应该返回false,否则点击事件就无法监听到。
4、View的onTouchEvent方法默认都会消费掉事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)。
查看原文:事件分发机制详解