自定义View从实现到原理(三)- 源码解析View的事件分发机制

自定义View从实现到原理(三)

上一篇博客我们讲到,在我们点击屏幕时,就会产生点击事件,这些点击事件被封装为了一个类:MotionEvent,而这个时候,我们的点击事件会先传递给当前的Activity,所以我们也有必要了解一下Activity的组成,详细的可以看我上一篇博客:
自定义View从实现到原理(二)- 源码解析Activity的构成

那么现在我们就正式进入View的事件分发机制。

源码解析View的事件分发机制

View的事件分发机制

点击事件发生之后,首先传递给Activity,这回就会调用Activity的dispatchTouchEvent()方法,根据我们上一篇所讲,这些具体的事件处理顺序自然就是:Activity中的PhoneWindow — PhoneWindow中的DecorView — DecorView的ViewGroup这个顺序。了解了这些之后,我们直接看最后一步,也就是ViewGroup的dispatchTouchEvent()方法,主要的部分代码:

 /**
     * 将触摸屏事件向下传递到目标视图,或者当前视图
     * 如果现在就是目标的话。
     *
     * @param ev 要向下分派的运动事件
     * @return 如果事件由视图处理,则为True,否则为false。
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
    ****
    if (actionMasked == MotionEvent.ACTION_DOWN) {
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }
    if (actionMasked == MotionEvent.ACTION_DOWN
    		|| mFirstTouchTarget !=null) {//1
    	final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//2
    	if(!disallopwIntercept){
    		intercepted = onInterceptTouchEvent(ev);
    		ev.setAction(action);
    		}else{
    			intercepted = false;
    			}
    	}else{
    		intercepted = true;
    		}
    	****
    	return handled;
    }

首先进行了判断,判断点击事件是否为DOWN事件,如果是的话那么下面这两个函数将会进行初始化工作,resetTouchState()方法中会把下面进行判断的mFirstTouchTarget的值置为null。

为什么要进行初始化?

因为一个完整的点击事件序列是以DOWN开始,UP结束的,因此如果是DOWN事件开始的话,那么就证明是一个新的点击事件,因此需要进行初始化。

我们接着看下面部分,看标注1的代码部分,这一部分的判断中,mFirstTouchTarget代表的就是当前的ViewGroup是否对点击事件进行了拦截,拦截了的话mFirstTouchTarget就会是null; 如果没有拦截则会交由子View进行处理,这个时候的mFirstTouchTarget !=null。我们假设当前的ViewGroup拦截了此点击事件:

此时mFirstTouchTarget =null,如果这是触发了ACTION_DOWN我们就会进入第一个if,也就是标有2处的代码部分,这个时候我们就会执行onInterceptTouchEvent(ev)方法,那么相对触发的是ACTION_UP以及ACTION_MOVE时则就不会触发onInterceptTouchEvent(ev)方法,而是直接将intercepted置为true,此后的一个事件序列均由这个ViewGroup进行处理。再看一下注释2的位置那一串代码,出现了一个FLAG_DISALLOW_INTERCEPT标志位,作用主要是制止ViewGroup拦截除了DOWN以外的事件。

这一部分我的总结就是:ViewGroup进行事件拦截的时候,这个事件的后续序列都会交给它来处理(ACTION_UP以及ACTION_MOVE),这个时候就无需调用onInterceptTouchEvent(ev)这个方法,所以我们知道,这个方法事件不是每次都会被调用的,接下来我们看一下这个方法的代码:

public boolean onInterceptTouchEvent(MotionEvent ev){
		return false;
	}

我们可以看到这个方法返回值是false,意味着不会进行事件拦截。由此我们就可以知道,如果我们自定义的ViewGroup想要拦截事件时,有必要重写这个方法。

继续看上面dispatchTouchEvent()方法的其他重要部分代码:

public boolean dispatchTouchEvent(MotionEvent ev) {
****
	for(int i = childrenCount - 1; i >= 0; i--){ //1
		****
		if(!canViewRecrivePointerEvents(child)
			|| !isTransformedTouchPointInView(x,y,child,null)){ //2
			ev.setTargetAccessibilityFocus(false);
			continue;
			}
		****
		if(dispatchTransformedTouchEvent(ev,false,child,idBitsToAssign)){ //3
			****
			}
		****
	}
	****
}

我们来梳理一下上面的几行关键代码:在1处的是一个for循环,这个循环遍历的是ViewGroup的子元素,也就是下面的ViewGroup以及子View,根据接下来的判断来决定子元素是否能够接收到点击事件,如果有子元素可以接收的话,那么就交给子元素进行处理这个事件。我们看一下这个遍历,i–,这个遍历是倒序遍历,即从ViewGroup的最上层子View开始进行处理判断;

在2处的是一个if判断,判断的是这个点击事件的触摸点是否在子View的范围内或者这个子View是否是在进行播放动画,如果都不符合的话,那么就执行continue语句来表示这个子View并不符合要求,进行遍历下一个子View;(代码中写的是!,但是读的时候还是要按照正常想法阅读,就这样:如果点击事件的触摸点不在子View的范围内或者这个子View没有在进行播放动画,那么这个View排除);

在3处的是另一个判断,我们来看一下这个dispatchTransformedTouchEvent()方法的代码:

private boolean dispatchTransformedTouchEvent(MotionEvent event , boolean cancel , 
						View child , int desiredPointerIdBits){
	final int oldAction = event.getAction();
	if(cancel || oldAction = MotionEvent.ACTION_CANCEL){
		if(child == null){
		handled = super.dispatchTouchEvent(event);
		}else{
		handled = child.dispatchTouchEvent(event);
		}
		****
		return handled;
	}
	****
}

这一部分很好理解,判断是否有子View,如果有的话调用子View的dispatchTouchEvent()方法,如果没有的话就调用super.dispatchTouchEvent()方法,由于ViewGroup也是继承自View的,那么我们去看一下这个super的dispatchTouchEvent()的方法,即View的方法:

public boolean dispatchTouchEvent(MotionEvent event){
	****
	boolean result = false;
	if(onFilterTouchEventForSecurity(event)){
		ListenerInfo li = mListenerInfo;
		if(li !=null && li.mOnTouchListener != null
				****
				&& li.mOnTouchListener.OnTouch(this,event)){
				result = true;	
			}
		if(!result && onTouchEvent(Event)){
			result = true;
		}
	}
	****
	return result;
} 

如果OnTouchListener不为null,而且onTouch方法返回true,那么表示的就是事件正在被消费(我理解的就是处理),这个时候我们就不会执行OnTouchEvent(event)方法,否则就会执行。接下来我们就看一下onTouchEvent(event)方法的代码:

public boolean onTouchEvent(MotionEvent event){
	****
	final int action = event,getAction();
	if(((viewFlags & CLICKABLE) == CLICKABLE || 
		(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
		(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE)}{
		switch(action){
			case MotionEvent.ACTION_UP:
			****
			performClick();
			****
		}
		return true;
	}
	return false;
}

在上面的判断中我们可以看出,只要View的CLICKABLE或者LONG_CLICKABLE中有一个是true的话,那么onTouch就会返回true来消耗接受处理这个事件,上面这两个我们自然都不会陌生,在View中设置setOnClickListenter以及setOnLongClickListener即可设置,他们会自动设置为上面那两个----CLICKABLE以及LONG_CLICKABLE,接着在ACTION_UP这个事件中会调用performClick()这个方法:

public boolean performClick(){
	****
	if(li != null && li.mOnClickListener != null){
		****
		li.mOnClickListener.onClick(this);
		return true;
	}
	****
}

截取了很简单的一部分,就是一个判断,这个判断的意思就是,如果View设置了点击事件OnClickListener,那么它设置的onClick方法就会进行执行。

到这里View的事件分发机制源码分析基本就到这里了,是不是一头雾水,别急,接下来我们来梳理一下点击事件分发的传递规则:

点击事件分发的传递规则

简单的来说,点击事件的MotionEvent在View中的层级传递就是点击事件分发,根据上面所看的源码,我们主要可以总结出三个重要的方法,点击事件中有这三个方法:

方法作用
dispatchTouchEvent(MotionEvent ev)用来进行事件的分发
onInterceptTouchEvent(MotionEvent ev)用来进行事件拦截,在dispatchTouchEvent(MotionEvent ev)中调用,只有ViewGroup中存在,View中并没有
onTouchEvent(MotionEvent ev)用来处理点击事件,在dispatchTouchEvent(MotionEvent ev)中调用

onInterceptTouchEvent(MotionEvent ev) 以及 onTouchEvent(MotionEvent ev) 都会在dispatchTouchEvent(MotionEvent ev)这个方法中进行调用,我们来梳理一下点击事件分发的传递规则:
1.点击事件产生后被封装为MotionEvent类交予Activity进行处理;
2.Activity交予PhoneWindow,交予DecorView,交予顶层的ViewGroup;
3.对于根ViewGroup,点击事件首先传递给他的dispatchTouchEvent(MotionEvent ev) 方法;
4.调用onInterceptTouchEvent(MotionEvent ev)方法;
5.1:返回true,证明要拦截这个事件,那么这个事件就会交给他的onTouchEvent()方法处理;
5.2:返回false,表示不拦截,那么就会判断是否有子元素;
5.2.1:如果有的话就会交给子元素的dispatchTouchEvent(MotionEvent ev),紧接着从3开始;
5.2.2:如果没有的话就会调用super的dispatchTouchEvent(MotionEvent ev)方法,通常最终会调用View的dispatchTouchEvent(MotionEvent ev),一般来说最终就会调用View的onTouchEvent(MotionEvent ev)方法。
6.在底层View的onTouchEvent()方法返回值是true时,
则会执行底层View的onTouchEvent()命令,
如果返回值时false的话,就证明并没有完成,所以result会被置为false,
接下来这样的话底层View的dispatchTouchEvent()方法也会返回false,
然后父View的dispatchTouchEvent()方法的handled会因为View的dispatchTouchEvent()方法返回false从 而被置为false,
这样父View的则会调用onTouchEvent()方法,以此类推,直到返回true;

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值