事件分发机制

事件分发机制:

事件分发机制主要有三个方法,分别是:diapatchTouchEvent() , onInterceptTouchEvent() , onTouchEvent()。

用一段伪代码来表示它们之间的关系:

	//对于一个根ViewGroup来说,点击事件会传递给dispatchTouchEvent()方法
	public boolean dispatchTouchEvent(MotionEvent ev){
		boolean consume = false;
		//如果onIntercepteTouchEvent()返回为true,则表示它要拦截当前事件,接着事件将会交由该ViewGroup处理
		if(!onInterceptTouchEvent(ev)){
			//它的onTouchEvent将会被调用
			consume = onTouchEvent(ev);
		}else{
			//如果onInterceptTouchEvent()方法返回的是false,当前事件将会继续传给他的子元素,一直反复这个过程
			consume = dispatchTouchEvent(ev);
		}
		return consume;
	}

一个点击事件的产生:

同一个事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻。在你这个过程将产生一系列的事情,这个事情的序列以down开始,中间含有数不清的move,最终以up结束。     down ---> move.......move -------> up。

当一个view需要处理事件时,如果它设置了onTouchListener,那么onTouchListener中的onTouch方法将会被调用。如果onTouch方法中返回的是false的话,那么当前view的onTouchEvent()方法将会被调用。如果它返回的是true,那么它的onTouchEvent方法将不会被调用。由此可见:onTouchEventListener()和onTouchEvent()的优先级不同,onTouchListener()的优先级 >  onTouchEvent()。这里在拓展讲一下点击事件的优先级,onClickListener(),较前两者它的优先级是最低的。即:onTouchListener()> onTouchEvent()> onClickListener()。


点击事件的传递:

点击事件的传递顺序:Activity---->Window-------->顶级View,然后顶级view再对事件进行分发。

下面具体分析一种情况:如果某view的onTouchEvent返回为false,那么它的父容器的onTouchEvent方法将被调用。为这个过程画一个图,便于大家理解:


那么事件是如果传到activity呢?

点击事件由MotionEvent来表示,


结论性话语:

1、事件序列:down ---> move.......move -------> up。

2、一个事件只能被一个view拦截且消耗。

3、某个view一旦决定拦截事件,那么这一个事件序列都只能由它处理,并且它的onInterceptTouchEvent方法是不会再调用的。

4、某个view一旦开始处理事件,如果它不消耗ACTION_DOWN事件(即:它的onTouchEvent方法返回为false),那么同一事件序列当中的其它事件也不会交由它处理。那么事件就会往上传递,交给它的父元素处理,即父元素的onTouchEvent方法将会被调用,以此内推。如果父元素一指没消耗此事件,那么它会一直向上传递,直到传给activity,如果activity还没有消耗掉的话,就会报异常了。

5、如果view消耗了ACTION_DOWN事件,而不消除action_down以外的事件,那么这个点击事件会消失。此时父元素的onTouchEvent方法不会被调用。最终这些消失的事件将会传递给activity处理。

6、view是没有onInterceptTouchEvent方法,它直接交予onTouchEvent()方法处理。

7、viewGroup默认不拦截任何事件。因为在android源码中,viewGroup 的 onInterceptTouchEvent()方法返回的是false

8、view的默认都是消耗事件的。因为在android源码中,view的onTouchEvent() 返回为true,除非它是不可点击的。对于view的是否可点击是这样的:longClickable()默认是false;clickable()要看具体情况,如Button默认为true,TextView为false。

9、view的enable属性不影响onTouchEvent()的默认返回值。

10、onClick发生的前提是当前view是可点击的,并且它收到了down和up的事件。

11、事件传递总是先传递给父元素,然后再由父元素分发给子元素的。通过requestDisallowInterceptTouchEvent()的方法可以在子元素中干预父元素的分发过程。


事件最先传递给当前activity,由activity的dispatchTouchEvent进行分发。

下面我们看一下这段activty的 dispatchTouchEvent源码:

//当一个view需要处理事件时
	public boolean dispatchTouchEvent(MotionEvent ev){
		if(ev.getAction() == MotionEvent.ACTION_DOWN){
			onUserInteraction();
		}
		if(getWindow().superDispatchTouchEvent(ev)){
			return true;//这里说明,如果如果子view的 diapatchTouchEvent 方法如果返回true的话,整个事件循环就结束来
			//如果返回的是false 那么就说明子view没有把这个事件给消费掉,这个事件将会传给activity的diapatchTouchEvent方法
		}
		return onTouchEvent(ev);
	}

我们想知道window是如何将事件传给 viewGroup(根view) 的,通过源码我们知道window是一个抽象类,而window的 superDispatchTouchEvent 方法也是一个抽象方法,因此我们要找到window的实现类才可。

通过源码查找,phoneWindow 就是window的实现类。

那么我们看一下phoneWindow是如何实现点击事件的。在phoneWindow 中,我们可以看到

window如何将事件传递给viewGroup:


	//activity的 dispatchTouchEvent 方法
	public boolean dispatchTouchEvent(MotionEvent ev){
		if(ev.getAction() == MotionEvent.ACTION_DOWN){
			onUserInteraction();
		}
		//从这里可以看出 activity 的dispatchTouchEvent 方法,将会调用 window 的 superDispatchTouchEvent 方法
		if(getWindow().superDispatchTouchEvent(ev)){
			return true;//如果子view的 diapatchTouchEvent 方法如果返回true的话,整个事件循环就结束来
			//如果返回的是false 那么就说明子view没有把这个事件给消费掉,这个事件将会传给activity的 onTouchEvent 方法
		}
		return onTouchEvent(ev);
	}
	
	//Window是一个抽象类,它的实现类是 phoneWindow ,这里是phoneWindow的 superDispatchTouchEvent 方法
	
	public boolean superDispatchTouchEvent(MotionEvent event){
		//由这里可以看出,此时 phoneWindow 又将调用 DecorView 的 superDispatchTouchEvent 方法
		return mDecor.superDispatchTouchEvent(event);
	}
	
	//那么这个 DecorView 又是什么东西呢 ?
	private final class DecorView extends FrameLayout implements RootViewSurfaceTaker{
		public final View getDecorView(){
			if(mDecor == null){
				installDecor();
			}
			return mDecor;
		}
	}
	//看看上面的代码我们知道,事件传递到 DecorView 这里,由于 DecorView 继承自 FrameLayout 且是父 view,所以最终事件会传递给view
	/**
	 * 为什么 DecorView 是父view呢,因为 getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0)
	 * 可以获取activity所置的 view , 这个view是 activity的setContentView 所设置的view 的父view 。
	 */

那么顶级view又是怎样把事件传递给子view的呢?

如果事件达到顶级view后,如果是viewGroup的情况,首先会调用viewGruop的 dispatchTouchEvent 方法

    //下面这是viewGroup的dispatchTouchEvent 的具体方法
    final boolean intercepter;
    //如果viewGroup不拦截事件,并将事件传递给子元素 则此时 mFirstTouchTarget != null
    if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null){
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if(!disallowIntercept){
            //如果决定不拦截事件,则onInterceptTouchEvent将会被调用
            intercept = onInterceptTouchEvent(ev);
            ev.setAction(action);
        }else{
            //如果决定拦截事件,则onInterceptTouchEvent 方法不会再被调用
            intercept = false;
        }
    }else{
        //onInterceptTouchEvent 方法不会再被调用
        intercept = true;
    }

在一种特殊情况下:如果在子view中设置 requestDisallowInterceptTouchEvent() 为 FLAG_DISALLOW_INTERCEPT,viewGroup将无法拦截action_down以外的事件。为什么action_down要除外呢,因为面对action_down事件时,ViewGroup总会调用自己的 onIterceptTouchEvent()方法来询问自己是否要拦截。

下面我们看一下为什么在子view中设置FLAG_DISALLOW_INTERCEPT 时,  会拦截 action_down 事件,看下面源码:

	//ViewGroup会在action_down 事件道来之前做重置状态操作
	if(actionMasked == MotionEvent.ACTION_DOWN){
		cancelAndClearTouchTargets(ev);
		resetTouchState();//在此方法当中,会对FLAG_DISALLOW_INTERCEPT进行重置
	}

当viewGroup不拦截事件的时候,事件会向下分发。

如何下发的,分析源码:

首先会遍历 ViewGroup 的所有子元素,然后判断子元素是否可以接收到点击事件,是否能够接收到子元素的判断依据:

1、子元素是否在播放动画

2、点击事件的坐标是否落在子元素的区域内。

如果某个子view满足这两个条件,那么事件就交由这个子view处理。

下面我们看一下 View 对点击事件的处理过程:

View对点击事件的处理的稍微简单一点,因为view是一个单独的元素,它没有子view,因此无法向下传递,所以它只能自己处理事件。从源码中可以看出view对点击事件的处理,首先它会判断有没有onTouchListener() 方法,如果有 且onTouchListener 方法返回的是true,那么onTouchEvent()就不会被调用了。

下面看一下onTouchEvent()方法是如何消耗事件的?

从源码当中可以看出:只要view的 clickable 和 long_clickable 有一个为true,那么这个事件就会消耗掉,即onTouchEvent() 方法返回为true。

这里说一下 默认view的 long_clickable 为false,而clickable要视具体view而定。在源码中我们可以看出:

setClickable() 和 setLongClickable() 可以分别改变view 的CLICKEABLE 和 LONG_CLICKABLE的属性,将其设置为true。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值