这两天的文章都是直接正如主题,没有什么开头的描述的,因为刚开始写文章,也不知道说什么。啊,今天天气不错哈。
那么今天呢我讲一下这个View的事件分发机制,这个很多面试的时候都会问到,因为这是View的一个核心点,对View的点击事件的一个分发的机制,所谓的事件分发机制呢,其实就是用户对这个View进行触碰产生的一个运动事件,比如说:点击事件,长按事件这些,这个事件产生了以后呢系统要把这个事件传递给一个View来进行消费,而这个传递的过程就是分发过程。事件分发过程是由三个方法来完成的
public boolean dispatchTouchEvent(MotionEvent ev)
用来进行事件分发,只要你触碰了任何控件,都会调用这个方法,主要是判断是当前view消费还是往下级view传递。
public boolean onInterceptTouchEvent(MotionEvent ev)
这个方法用来判断是否拦截这个事件,默认为false也就是不拦截,需要进行拦截的时候在dispatchTouchEvent内部调用。
public boolean onTouchEvent(MotionEvent event)
用来处理当前事件,返回结果表示是否消费,如果不消费,则这个事件不会再接收到。
那么他们三个有什么不可描述的关系呢,首先我们要先知道一个点击事件产生后的传递过程,当一个事件产生一定会先传递给当前Activity,然后Activity再传递给window,window会传递给当前页面的顶级View,最后这个View接收到事件以后再按照事件分发机制去分发事件。这里有一种情况就是,如果一个View的onTouchEvent返回了false,那么他的父容器的onTouchEvent也会被调用,以此类推,所有的View都不消费当前事件的话,那么最终这个事件会传递给Activity,Activity的onTouchEvent会进行消费掉。这里采用一个很好的例子,是从别的书籍上看到的,一个领导分配了一个很难的任务给你,然后你不会(onTouchEvent返回false),那么怎么办呢,只能交回给上级(上级的onTouchEvent被调用),那如果你的上级也搞不定呢,那就交给上级的上级,到最后最顶级的上级那里,进行消费掉。这个例子是不是很贴近生活,哈哈我也是从别得地方看到的。
那么我讲了这么半天大家也肯定还不是很懂,那么我们从源码入手进行分析,打开你们的studio,找到Activity,在Activity里面有个方法叫
public boolean dispatchTouchEvent(MotionEvent ev) {
if(ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if(getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
通过上面的代码我们可以看出来,Activity将时间交给附属的Window进行分发,如果返回true的话,那么事件循环就结束了,如果返回false的话就
代表着事件没人处理,那么最后会交给Activity的onTouchEvent处理。
那么我接着Window往下看,Window是怎么处理这个事件的呢,我们看源码知道了Window是个抽象类,抽象类怎么处理呢?所以我们要找他Window的实现类PhoneWindow,我们找到PhoneWindow的dispatchEvent方法
@Override
public boolean superDispatchTrackballEvent(MotionEvent event) {
return mDecor.superDispatchTrackballEvent(event);
}
通过上面的代码我们可以看到PhoneWindow把事件传递给了mDecor,那么这个mDecor是什么呢,我继续往下看
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
那么我们可以看到,mDecor是DecorView,那么这个DecorView又是什么呢,注意看上面的注释,翻译过来就是,这是窗口的顶级视图,包含窗口装饰。也就是说,PhoneWindow把事件传递给了DecorView也就是顶级视图,而DecorView是继承FrameLayout,而FrameLayout又是继承的Viewgroup,也就是说,事件一定会传递到Viewgroup里面去,所以我们直接去看Viewgroup的dispatchTouchEvent是怎么处理的。
// Check for interception.
final booleanintercepted;
if(actionMasked == MotionEvent.ACTION_DOWN||mFirstTouchTarget!=null) {
final booleandisallowIntercept = (mGroupFlags&FLAG_DISALLOW_INTERCEPT) !=0;
if(!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);// restore action in case it was changed
}else{
intercepted =false;
}
}else{
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted =true;
}
Viewgroup的dispatchTouchEvent的代码逻辑比较多,我们就分段说明,先看上面这段代码,上面注释Check for interception.表示的意思是检查拦截,也就是这段代码的逻辑是是否拦截处理这个事件,然后我们从代码中可以看出,Viewgroup在两种情况下会判断是否拦截事件,那两种呢,一个是MotionEvent.ACTION_DOWN也就是按下的时候,还有一个是mFirstTouchTarget!=null的时候,那这个是什么情况呢,这里我们可以从后面的代码中可以看出来,在后面的代码中我们可以看出如果ViewGroup的子元素处理成功时,mFirstTouchTarget会被赋值并指向子元素,也就是当Viewgroup不拦截事件交由子元素处理时,mFirstTouchTarget !=null,反过来说,ViewGroup如果拦截了当前事件的话,那么(actionMasked == MotionEvent.ACTION_DOWN||mFirstTouchTarget!=null)这个判断就会不成立返回false,导致ViewGroup中的onInterceptTouchEvent不会再被调用,并且同一事件系列的其他事件也会默认交给他处理。
我们接着再看ViewGroup不拦截事件交由子元素处理的时候,看源码:
finalView[] children =mChildren;
for(inti = childrenCount -1; i >=0; i--) {
final intchildIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
finalView child =getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if(childWithAccessibilityFocus !=null) {
if(childWithAccessibilityFocus != child) {
continue;
}
if(!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y, child,null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if(newTouchTarget !=null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits|= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if(dispatchTransformedTouchEvent(ev,false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime= ev.getDownTime();
if(preorderedList !=null) {
// childIndex points into presorted list, find original index
for(intj =0; j < childrenCount; j++) {
if(children[childIndex] ==mChildren[j]) {
mLastTouchDownIndex= j;
break;
}
}
}else{
mLastTouchDownIndex= childIndex;
}
mLastTouchDownX= ev.getX();
mLastTouchDownY= ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget =true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if(preorderedList !=null) preorderedList.clear();
}
上面这段代码呢,我们可以看出来在遍历ViewGroup所有的子元素,然后通过子元素是否在播动画和点击事件的坐标是否在子元素的区域内来判断子元素是否能够接受这个事件。如果这两个条件都满足就交给这个子元素处理,我们看到dispatchTransformedTouchEvent这个方法,点击进去看,你会发现
if(child ==null) {
handled =super.dispatchTouchEvent(event);
}else{
handled = child.dispatchTouchEvent(event);
}
他在判断child也就是子元素是否为空,不为空的话就交给子元素处理了,从而完成了一轮事件分发。如果子元素的dispatchTouchEvent返回true,这时我们暂时不考虑事件在子元素内部是怎么处理的,那么mFirstTouchTarget就会被赋值同时跳出for循环,看源码
newTouchTarget= addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget =true;
break;
这里完成了mFirstTouchTarget的赋值并终止对子元素的遍历。吐过dispatchTouchEvent返回false,ViewGroup就会把事件分发给下一个子元素(如果还有下一个子元素的话)。
看到上面,你们一定会想mFirstTouchTarget是在哪里赋值的啊,我们看addTouchTarget这个方法
privateTouchTarget addTouchTarget(@NonNull View child,intpointerIdBits) {
finalTouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next=mFirstTouchTarget;
mFirstTouchTarget= target;
returntarget;
}
mFirstTouchTarget是一种单链表结构,mFirstTouchTarget是否被赋值直接影响到ViewGroup对事件的拦截,如果mFirstTouchTarget为空,那么ViewGroup将默认拦截同一序列中的所有点击事件。
今天就先讲ViewGroup的事件分发,如果大家觉得还是不理解的话,就去按照郭神的例子敲一下,这里附上一个连接:http://blog.csdn.net/guolin_blog/article/details/9153747
谢谢大家对我的支持。