写这篇文章实际上是有缘由的,说实话此次面试真的很失败,看着身边的人都拿到了高薪的工资,感受本身仍是有些惭愧。也更说明本身在不少方面的知识点仍是不够扎实,因而再一次拿起了view的事件分发的源码给看了一遍。java
android中事件分发机制是android中常见的问题,通常你们都知道view的分发事件是从view的Viewgroup(Parent)#dispatchTouchEvent
到Viewgroup(Parent)#onInterceptTouchEvent
再到View#dispatchTouchEvent
,而后到view的onTouchEvent
,最后又回到了Viewgroup(Parent)#onTouchEvent
。若是你们记不住方法名,能够直接说先是parent的分发到拦截再到view的分发,再到view的消费,最后到parent的消费android
这样回答确定是很浅显的,由于没有说出是否拦截、是否分发、是否消费的各类条件,没有涉及到各类action的分发状况,上面说的默认分发只是针对action_down的,由于view/viewgroup
各类super调用都是不进行分发、拦截、消费的,因此在没找处处理touch事件的view时候,是一直往上层view传递的,一直传到activity里面,下面咱们再来整理一下:面试
若是viewgroup不进行分发,那么
action_down
、action_move
和action_up
只会执行到viewgroup的dispatchTouchEvent
,不分发的条件是dispatchTouchEvent
直接返回true或false,true和false的区别是true会执行action_down
、action_move
和action_up
,而若是直接返回false只会执行到action_down。而且后续的viewgroup的onInterceptTouchEvent
后续方法都不会被执行到。bash
关于为何view/Viewgroup的dispatchTouchEvent
返回true的时候三个action都能执行到,而返回false的话,只能执行到action_down,这个须要到view/Viewgroup的父类中dispatchTouchEvent
找答案,该方法中会在action_down的时候调用dispatchTransformedTouchEvent
方法,而该方法是经过子view的dispatchTouchEvent方法的返回值来决定父类的dispatchTransformedTouchEvent
方法的返回值,而dispatchTransformedTouchEvent
的返回值会决定mFirstTouchTarget
是否为空,因此在action_down的过程当中实际中经过子view的dispatchTouchEvent
方法返回值来肯定mFirstTouchTarget
是否为空。这里贴出viewgroup中dispatchTransformedTouchEvent
方法的删减代码:学习
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
------------------
//省略了cancel部分的代码
------------------------
//若是child为空,直接调用本身的dispatchTouchEvent方法,此时本身就至关于一个view,touch事件走本身的
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
//返回值直接经过孩子来获取返回值
handled = child.dispatchTouchEvent(transformedEvent);
}
transformedEvent.recycle();
return handled;
}
复制代码
因此若是view/viewgroup的dispatchTouchEvent
方法返回false,表示在action_down的时候,父类的dispatchTransformedTouchEvent
方法返回false;若是返回true会调用addTouchTarget
方法,给mFirstTouchTarget
设置值:ui
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
复制代码
紧接着在在后面又会调用了:spa
dispatchTouchEvent
返回false的时候,才会走这里,因此后面的
action_move
和
action_up
都会走这里,而此时传入的child=null,从上面代码能够看到,直接调用了父类的
dispatchTouchEvent
方法。因此从这里不难看出在view/viewgroup的
dispatchTouchEvent
返回false的时候直接调用了父类的
dispatchTouchEvent
方法,所以只有action_down事件。
其实这道题考察你们对view的dispatchTouchEvent和view的onTouchEvent事件的处理流程,上面已经分析了想要view能执行到view的touch事件,那么必需要求view的dispatchTouchEvent
返回true,而dispatchTouchEvent
返回true要么是dispatchTouchEvent
直接返回true或者view的onTouchEvent
返回true。若是从效率上看,直接将dispatchTouchEvent
返回true就ok,而不须要再去关心onTouchEvent
方法。设计
关于拦截无非就是拦截或不拦截,而拦截的条件是返回true,不拦截是返回false或返回super.onInterceptTouchEvent,默认的super是返回false的,所以能够用super表示不拦截日志
viewgroup拦截实际是经过在dispatchTouchEvent
方法中,设置intercepted变量,若是在拦截方法里面返回true,那么intercepted为true,若是为true则在action_down的时候mFirstTouchTarget=null,那么此时是直接调用dispatchTransformedTouchEvent
传入的child=null,所以将事件交给了super.dispatchTouchEvent
,此时把它当成一个view来处理了。code
先贴出事例代码:
testView在testViewgroup里面,testViewgroup在action_move的时候拦截(onInterceptTouchEvent在move返回true),testView不进行分发(dispatchTouchEvent返回true) 咋们经过log来看结果:
mFirstTouchTarget
不为空,紧接着在action_move的时候进行了拦截,则intercepted=true,
既然在move过程当中肯定了intercepted=true,mFirstTouchTarget不为空,则能够看viewgroup.dispatchTouchEvent部分代码:
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
//alreadyDispatchedToNewTouchTarget=false
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//因为move过程当中intercepted=true,则cancelChild=true
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//看到了没这里就是触发child的dispatchTouchEvent的action_cancel
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
//因为next=null,所以mFirstTouchTarget=null,因此在action_move刚进来的时候mFirstTouchTarget=null了,
//待会咱们经过反射看下该变量
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
复制代码
上面也说明了在action_move进来的时候先是触发了testView#dispatchTouchEvent的action_cancel,紧接着mFirstTouchTarget
=null了,因为mFirstTouchTarget
是viewgroup类中私有的变量,咱们能够经过反射调用该变量看下是否为空:
//反射代码,关于反射能够看我以前的文章java反射整理
mFirstTouchTarget
属性:
经过上面能够验证刚才move过程当中mFirstTouchTarget
为空的判断,日志以下:
mFirstTouchTarget
还不是null,第二次move的时候就是null了,所以在后续的move和up过程当中,只会此处:
因此针对上面面试官提的问题,你们知道怎么说了吧,仍是针对该问题作个小结:
先是down事件会通过viewgroup的dispatchTouchEvent,再到viewgroup的onInterceptTouchEvent,最后到view的dispatchTouchEvent,此时mFirstTouchTarget不为空,紧接着到了move首先到viewgroup的dispatchTouchEvent,再到viewgroup的onInterceptTouchEvent,因为在move过程当中拦截了,紧接着走view的dispatchTouchEvent的action_cancel,此时接着把mFirstTouchTarget至为null,所以后续的move和up事件只会走viewgroup的dispatchTouchEvent和onTouchEvent。 画出一张图来给你们看下:
好了,关于这个问题告一段落了,若是分析有问题,你们能够提出疑问。
其实这个问题在上面分析中已经分析过了,testview的onTouchEvent中消费,因此在action_down中mFirstTouchTarget
不为空,所以在action_move和action_up中mFirstTouchTarget
仍是不为空,因此无论手指是否已经离开了testview,action_move和action_up仍是会走testview的dispatchTouchEvent和onTouchEvent。
首先肯定action_down过程当中mFirstTouchTarget
是否为空,若是不为空,因此无论手指是否已经不在testView上了,action_move和action_up仍是会在testView的onTouchEvent上进行消费的。
这个问题就没涉及viewgroup到view的事件传递,onTouch指setOnTouchListener的回调方法,它是优先于onTouchEvent事件的,你们能够看下view的dispatchTouchEvent中有以下代码:
onClick事件是在onTouchEvent消费事件中的action_up触发的,onTouch是在dispatchTouchEvent中触发的,因此onTouch要先于onClick事件,咱们也能够经过onTouch返回true来屏蔽掉onClick事件。
好了,关于此次我面试中遇到的事件分发主要是上面这几个问题,你们有什么其余的问题,能够在评论区互动。
其实android事件分发核心是在
viewgroup
的dispatchTouchEvent
的action_down
过程当中找到mFirstTouchTarget
是否为空,经过反序遍历子view的dispatchTouchEvent的方法,若是发现有一个子view的dispatchTouchEvent
方法返回true,那么mFirstTouchTarget
就不为空,不然为空。若是mFirstTouchTarget
不为空,那么action_move
和action_up
才会往下传递,若是在action_move
和action_up
过程当中有viewgroup拦截了事件,则此时先向子view的dispatchTouchEvent
传递一个action_cancel
,而且将mFirstTouchTarget
至为null,因此此时action_move
和action_up
只会走viewgroup
的dispatchTouchEvent
和onTouchEvent
;若是mFirstTouchTarget
在action_down
过程当中就已经null的话,则从action_down
一直向上层view传递,不会有后续的action_move
和action_up
了。
其实看到身边不少朋友抱怨本身的工资很低,包括笔者也是同样的,其缘由是在面试过程当中没有给面试官一个很好的答案。因此笔者会持续更新面试过程当中遇到的问题,也但愿你们和笔者一块儿进步,一块儿学习。好了,此次文章就到这里,有什么问题,欢迎你们互动。