最近在项目中遇到了touch冲突的问题,自己在网上看了一些大神的文章,但之后还是似懂非懂的,感觉理解的不够深刻,索性就自己写了个demo动手测试
先来看张图,
看完图意在对事件分发事件有个大概的认知,看不懂也没关系。
如果想要理解android事件分发机制,先来大概了解一下三个方法dispatchTouchEvent,onInterceptTouchEvent和onTouchEvent
dispatchTouchEvent:android监听到事件是通过此方法来向下分发的
onInterceptTouchEvent:这个方法只有ViewGroup才有,用来决定是否拦截事件的
onTouchEvent:事件最终被消费的地方
这个图先将就看着,接下来进入测试。
布局文件
<com.huxq.motioneventmytest.OutLinearLayout 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:gravity="center"
tools:context=".MainActivity" >
<com.huxq.motioneventmytest.MyImageView
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_gravity="center"
android:src="@drawable/ic_launcher" />
</com.huxq.motioneventmytest.OutLinearLayout>
OutLinearLayout.java
public class OutLinearLayout extends LinearLayout {
public OutLinearLayout(Context context) {
super(context);
}
public OutLinearLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public OutLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i("out", "dispatchTouchEvent ACTION_DOWN");
break;
// return true;
case MotionEvent.ACTION_MOVE:
Log.i("out", "dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i("out", "dispatchTouchEvent ACTION_UP");
break;
case MotionEvent.ACTION_POINTER_UP:
Log.i("out", "dispatchTouchEvent ACTION_POINTER_UP");
break;
default:
break;
}
// return true;
return super.dispatchTouchEvent(ev);
// return false;
}
private float oldX,oldY,newX,newY;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
oldX = ev.getX();
oldY = ev.getY();
Log.i("out", "onInterceptTouchEvent ACTION_DOWN");
break;
// return true;
case MotionEvent.ACTION_MOVE:
Log.i("out", "onInterceptTouchEvent ACTION_MOVE");
newX = ev.getX();
newY = ev.getY();
float moveX = newX-oldX;
float moveY = newY-oldY;
Log.i("moved", "moveX="+moveX+";moveY="+moveY);
if(moveX<moveY){
return true;
}/*else{
return false;
}*/
break;
case MotionEvent.ACTION_UP:
Log.i("out", "onInterceptTouchEvent ACTION_UP");
break;
case MotionEvent.ACTION_POINTER_UP:
Log.i("out", "onInterceptTouchEvent ACTION_POINTER_UP");
break;
default:
break;
}
// return true;
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i("out", "onTouchEvent ACTION_DOWN");
// return true;
break;
case MotionEvent.ACTION_MOVE:
Log.i("out", "onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i("out", "onTouchEvent ACTION_UP");
break;
case MotionEvent.ACTION_POINTER_UP:
Log.i("out", "onTouchEvent ACTION_POINTER_UP");
break;
default:
break;
}
return false;
// return super.onTouchEvent(event);
}
}
我的测试demo有三层布局 Activity Out imageview,因为out是ViewGroup,有所有的三个方法,所以下面的测试都是在out上面做修改的
01-06 09:48:09.816: E/MotionEvent(5373): Down
01-06 09:48:09.816: I/activity(5373): activity dispatchTouchEvent ACTION_DOWN
01-06 09:48:09.816: I/out(5373): out dispatchTouchEvent ACTION_DOWN
01-06 09:48:09.816: I/out(5373): out onInterceptTouchEvent ACTION_DOWN
01-06 09:48:09.816: I/imageview(5373): imageview dispatchTouchEvent ACTION_DOWN
01-06 09:48:09.816: I/imageview(5373): imageview onTouchEvent ACTION_DOWN
01-06 09:48:09.816: I/out(5373): out onTouchEvent ACTION_DOWN
01-06 09:48:09.836: I/activity(5373): activity onTouchEvent ACTION_DOWN
01-06 09:48:10.076: E/MotionEvent(5373): Move
01-06 09:48:10.076: I/activity(5373): activity dispatchTouchEvent ACTION_MOVE
01-06 09:48:10.079: I/activity(5373): activity onTouchEvent ACTION_MOVE
01-06 09:48:10.123: E/MotionEvent(5373): Up
01-06 09:48:10.123: I/activity(5373): activity dispatchTouchEvent ACTION_UP
01-06 09:48:10.123: I/activity(5373): activity onTouchEvent ACTION_UP
可以看到事件从actionDown开始,activity最先接受到事件,并且通过dispatchTouchEvent分发给out,然后out依次走dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent, 最后分发到了imageview的onTouchEvent,而onTouchEvent并没有消费它, 所以事件又接着向上冒泡返回,最终会返回到activity的ontouchevent。接下来的move up事件只在activity中处理不再分发了。
接下来我把out的dispatchTouchEvent 返回值从return super.dispatchTouchEvent(ev)改为return true。
01-06 09:50:09.178: E/MotionEvent(8107): Down
01-06 09:50:09.178: I/activity(8107): activity dispatchTouchEvent ACTION_DOWN
01-06 09:50:09.178: I/out(8107): out dispatchTouchEvent ACTION_DOWN
01-06 09:50:09.321: E/MotionEvent(8107): Move
01-06 09:50:09.321: I/activity(8107): activity dispatchTouchEvent ACTION_MOVE
01-06 09:50:09.321: I/out(8107): out dispatchTouchEvent ACTION_MOVE
01-06 09:50:09.401: E/MotionEvent(8107): Up
01-06 09:50:09.401: I/activity(8107): activity dispatchTouchEvent ACTION_UP
01-06 09:50:09.401: I/out(8107): out dispatchTouchEvent ACTION_UP
在没改之前,return super.dispatchTouchEvent(ev)代表把事件分发给子view,return true则是把事件自己消费了,再来试下return false
01-06 09:53:32.771: E/MotionEvent(12767): Down
01-06 09:53:32.771: I/activity(12767): activity dispatchTouchEvent ACTION_DOWN
01-06 09:53:32.771: I/out(12767): out dispatchTouchEvent ACTION_DOWN
01-06 09:53:32.771: I/activity(12767): activity onTouchEvent ACTION_DOWN
01-06 09:53:32.868: E/MotionEvent(12767): Move
01-06 09:53:32.868: I/activity(12767): activity dispatchTouchEvent ACTION_MOVE
01-06 09:53:32.868: I/activity(12767): activity onTouchEvent ACTION_MOVE
01-06 09:53:32.901: I/activity(12767): activity onTouchEvent ACTION_MOVE
01-06 09:53:32.948: E/MotionEvent(12767): Up
01-06 09:53:32.948: I/activity(12767): activity dispatchTouchEvent ACTION_UP
01-06 09:53:32.951: I/activity(12767): activity onTouchEvent ACTION_UP
显而易见,return false就说明告诉上级,我和我的子view都不需要处理这次的点击事件,所及接下来的事件与我无关,不要再发给我了。
通过以上的测试可以得出结论:dispatchTouchEvent(MotionEvent ev)方法中:
1、当return super.dispatchTouchEvent(ev)时,事件会被正常的分发下去;
2、当return true时,事件则被当前方法给消费了,后续的move up事件都会被当前方法接收到
3、当return false时,后续事件不会再分发到当前view了,相当于接下来的事件与我无关了。
仔细看第一个log,会看到有这一行
01-06 09:48:09.816: I/out(5373): out onInterceptTouchEvent ACTION_DOWN
而且onInterceptTouchEvent之后再也没出现过,先不急着下结论,接下来就测试下onInterceptTouchEvent的返回值
我把out的返回值从return super.onInterceptTouchEvent(ev)改为return true。
01-06 10:16:44.716: E/MotionEvent(9510): Down
01-06 10:16:44.733: I/activity(9510): activity dispatchTouchEvent ACTION_DOWN
01-06 10:16:44.733: I/out(9510): out dispatchTouchEvent ACTION_DOWN
01-06 10:16:44.733: I/out(9510): out onInterceptTouchEvent ACTION_DOWN
01-06 10:16:44.736: I/out(9510): out onTouchEvent ACTION_DOWN
01-06 10:16:44.736: I/activity(9510): activity onTouchEvent ACTION_DOWN
01-06 10:16:44.813: E/MotionEvent(9510): Move
01-06 10:16:44.813: I/activity(9510): activity dispatchTouchEvent ACTION_MOVE
01-06 10:16:44.813: I/activity(9510): activity onTouchEvent ACTION_MOVE
01-06 10:16:44.943: E/MotionEvent(9510): Up
01-06 10:16:44.943: I/activity(9510): activity dispatchTouchEvent ACTION_UP
01-06 10:16:44.943: I/activity(9510): activity onTouchEvent ACTION_UP
和第一个log相比,可以发现out直接没把事件分发给imageview,而是直接拦截下来交给了自己的onTouchEvent处理。
所以reutrn super.onInterceptTouchEvent(ev)就是不拦截,return true就是拦截。
那么return false呢?其实效果和return super.onInterceptTouchEvent(ev)一样,这个我测过了,这里就不贴出来了。其实仔细想想也是,
onInterceptTouchEvent只有两种业务需求:拦截和不拦截,不和上层view交互,与上层view交互的事情是由dispatchTouchEvent来做的,
所以就没有那么多需求,只要两种返回类型就可以了。
通过对onInterceptTouchEvent的测试可以得出结论:onInterceptTouchEvent(MotionEvent ev)方法中:
1、当return super.dispatchTouchEvent(ev)时,事件默认不拦截
2、当return true时,事件会被拦截下来,交给自己的onTouchEvent处理
3、当return false时,同return super.dispatchTouchEvent(ev)。
一种是子view没有处理事件,由子view冒泡冒上来的,
二是有当前viewGroup通过onInterceptTouchEvent返回true主动争取得到的。
好,先说这么多,接下来测试onTouchEvent的返回值,我把out的返回值从return super.onTouchEvent(ev)改为return true。
01-06 10:58:31.198: E/MotionEvent(27447): Down
01-06 10:58:31.198: I/activity(27447): activity dispatchTouchEvent ACTION_DOWN
01-06 10:58:31.198: I/out(27447): out dispatchTouchEvent ACTION_DOWN
01-06 10:58:31.198: I/out(27447): out onInterceptTouchEvent ACTION_DOWN
01-06 10:58:31.198: I/out(27447): out onTouchEvent ACTION_DOWN
01-06 10:58:31.265: E/MotionEvent(27447): Move
01-06 10:58:31.265: I/activity(27447): activity dispatchTouchEvent ACTION_MOVE
01-06 10:58:31.265: I/out(27447): out dispatchTouchEvent ACTION_MOVE
01-06 10:58:31.265: I/out(27447): out onTouchEvent ACTION_MOVE
01-06 10:58:31.298: I/activity(27447): activity dispatchTouchEvent ACTION_MOVE
01-06 10:58:31.298: I/out(27447): out dispatchTouchEvent ACTION_MOVE
01-06 10:58:31.298: I/out(27447): out onTouchEvent ACTION_MOVE
01-06 10:58:31.361: E/MotionEvent(27447): Up
01-06 10:58:31.361: I/activity(27447): activity dispatchTouchEvent ACTION_UP
01-06 10:58:31.361: I/out(27447): out dispatchTouchEvent ACTION_UP
01-06 10:58:31.361: I/out(27447): out onTouchEvent ACTION_UP
return true以后所有的事件都由这个方法来处理了,而在我没改之前,返回值还是 super.onTouchEvent(ev)的时候,
事件最终都是回到了activity的onTouchEvent中处理了。因为activity是事件的发起点也是事件的终点(如果事件没有被处理的话)。
我在测试的时候把事件分成了三种类型(其实还有更多种,不过测试事件分发目前有这三种就够了)Down Move和Up。
通过阅读上面的log,很容易就能发现这么一个规律:谁能处理所有的事件,就主要看谁能够在onTouchEvent中处理ACTION_DOWN。
也就是说,只要ACTION_DOWN事件能传到当前view的onTouchEvent方法中,并且你也返回了True,消费了它,那么后续的事件,
也会被当前view的onTouchEvent接收到,但收到归收到,至于你去不去处理它那就是另一回事了,如果没有处理的话,Move和Up还是会返回给activity的OnTouchEvent就收到的。
当然,和onInterceptTouchEvent方法一样,return super.onTouchEvent(ev)和return false效果是一样的,我就不贴了,最后我会把我测试的demo挂上来,有需要的可以自己具体去测试下。
最后做个总结:
Android的事件分发流程是这样的,起点是Activity,然后通过dispatchTouchEvent方法一层一层的向下进行分发,当事件分发给ViewGroup的时候,ViewGroup还有个onInterceptTouchEvent可以决定是否拦截事件,拦截了就交给viewGroup的onTouchEvent,不拦截就分发下去,最后分发到底层view,如果底层view没有处理,就返回给上层ViewGroup的onTouchEvent,如果都没有处理,最后会返回给Activity的onTouchEvent。
dispatchTouchEvent(MotionEvent ev)方法中:
1、当return super.dispatchTouchEvent(ev)时,事件会被正常的分发下去;
2、当return true时,事件则被当前方法给消费了,后续的move up事件都会被当前方法接收到
3、当return false时,后续事件不会再分发到当前view了,相当于接下来的事件与我无关了。
onInterceptTouchEvent(MotionEvent ev)方法中:
1、当return super.onInterceptTouchEvent(ev)时,事件默认不拦截
2、当return true时,事件会被拦截下来,交给自己的onTouchEvent处理
3、当return false时,同return super.onInterceptTouchEvent(ev)。
4、onInterceptTouchEvent只在ACTION_DOWN事件中处理是否拦截的逻辑,后续Move,Up事件onInterceptTouchEvent是不起作用的
onTouchEvent(MotionEvent ev)方法中:
1、当return super.onTouchEvent(ev)时,事件默认不接收
2、当return true时,事件会被接收处理。
3、当return false时,同return super.onTouchEvent(ev)。
4、如果ACTION_DOWN事件返回True,但后续Move,Up事件返回false,那么后续的事件该onTouchEvent方法依然能接收到,但不会处理它,会返回给上层的ViewGroup或者Activity的onTouchEvent,由他们来处理。ps:这条规则在dispatchTouchEvent好像也适合,好奇的朋友可以去试试。
如果有对事件分发有有不同意见的,欢迎留言讨论或者联系我,我的邮箱:huxq17@163.com QQ:1491359569