Android事件分发机制

最近在项目中遇到了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)


还有最后一个方法,onTouchEvent。通过上面的测试其实可以发现,有两种情况会调用这个方法,

一种是子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

测试Demo下载







  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值