民间闲散程序员
公众号ID:DeepAndroid
关注
本文主要介绍的事touch事件的分发与处理,涉及到的方法主要有三个:dispatchTouchEvent onInterceptTouchEvent onTouchEvent。
在开始前,先放一下demo地址:
https://github.com/mymdeep/gaobige
也可以点击阅读全文查看。
在开始之前我们先创建一个ViewGroup:
根据上图,我们建立了两个ViewGroup,一个是最外层的深蓝色(group1),然后是浅蓝色(group2),最里面放了一个红色的View。
我们在每个viewgroup的事件处理中加上log:
@Override
public booleandispatchTouchEvent(MotionEvent
ev){
Log.e("touch",name+" "+
"dispatchTouchEvent "
+getAction(ev.getAction()));
super.dispatchTouchEvent(ev);
returnisDispatch;
}
@Override
public booleanonInterceptTouchEvent(MotionEventev){
Log.e("touch",name+" "+"onInterceptTouchEvent "+getAction(ev.getAction()));
super.onInterceptTouchEvent(ev);
returnisIntercept;
}
@Override
public booleanonTouchEvent(MotionEvent
event){
Log.e("touch",name+" "+"onTouchEvent "
+getAction(event.getAction()));
returnisTouch;
}
点击红色区域看一下:
E/touch: Activity dispatchTouchEvent down
E/touch: Group1 dispatchTouchEvent down
E/touch: Group1 onInterceptTouchEvent down
E/touch: Group2 dispatchTouchEvent down
E/touch: Group2 onInterceptTouchEvent down
E/touch: View dispatchTouchEvent down
E/touch: View onTouchEvent down
E/touch: Group2 onTouchEvent down
E/touch: Group1 onTouchEvent down
E/touch: Activity onTouchEvent down
根据log,很容易看到事件的传递是先到最外层的dispatchTouchEvent和onInterceptTouchEvent然后依次往下传递,传到最底下的时候,再从下往上执行onTouchEvent。
现在知道了执行顺序,我们来看一下dispatchTouchEvent onInterceptTouchEvent onTouchEvent这三个方法都是干嘛的。
dispatchTouchEvent负责事件的分发,onInterceptTouchEvent负责事件的拦截,onTouchEvent负责事件的处理。这也说明了为什么会是上面那样的处理顺序,因为先要在最外层判断事件是否被拦截,如果拦截了,也就没有必要进行处理了。
那么如何进行事件的拦截呢?大家可以看到,dispatchTouchEvent onInterceptTouchEvent onTouchEvent这三个方法的返回类型都是布尔型,通过返回值可以进行拦截与不拦截。我们下面一个个进行分析。
dispatchTouchEvent
首先是dispatchTouchEvent,我们先看一下将所有viewgroup和view的dispatchTouchEvent返回值都设置成false,完整的log:
可以看到,我在执行了一个down->up操作的时候,viewgroup只执行了处理了一个down,没有处理up。这是为什么呢?
因为,当dispatchTouchEvent返回false之后,表示该viewgroup(view)不再处理后续事件。
我们可以再试一下,将viewgroup1的dispatchTouchEvent返回值改为true,其它还为false,看一下log:
viewgroup1处理事件了,但是由于其他view还是false,所以不再接收事件。
onInterceptTouchEvent
onInterceptTouchEvent就比较好理解了,当最外层的viewgroup的onInterceptTouchEvent返回false,表示不拦截事件,继续往下传递(一直传递到最后一层),当返回true时,表示这个viewgroup要消费这个touch事件,没必要往下传递了。
注意,view没有onInterceptTouchEvent方法。
这个很好验证,我们将viewgroup2的onInterceptTouchEvent返回true,让viewgroup2消费事件,然后,不让view再处理了。
通过这段log,很好看出,viewgroup2很好地消费了touch事件,没有让view再去处理。但是为什么viewgroup1也消费了呢?
这是因为分发顺序是自上而下的,viewgroup2拦截后,只能保证他下面的组件不在处理事件,没法保证上面的(父组件)不处理,那怎么才能保证只让viewgroup2处理,不让viewgroup1处理呢?这就是下面一个题目了。
onTouchEvent
onTouchEvent的返回值用来标识事件是否消费完,当返回true的时候标识事件已经消费完了,不需要父组件再去消费了,这也回答了上面的问题,只要viewgroup2的onTouchEvent返回true,viewgroup1就不会执行onTouchEvent处理事件了,我们将viewgroup2的onTouchEvent的返回值改为true试一下:
onTouchEvent为什么仍然向上派发?
这里有个小误区:所有的技术文章都会告诉你return true消费完事件不再上报了,其实并不是这样,这里还与dispatchTouchEvent有关。
我们将viewgroup2的dispatchTouchEvent和onTouchEvent都设置成true,其它所有均为false:
我们可以看到确实viewgroup1的onTouchEvent没有执行,但是Activity的onTouchEvent执行了,我们在将viewgroup1的dispatchTouchEvent改成true:
这次Activity的onTouchEvent也不执行了。
这样看来onTouchEvent是否向上派发,不仅与自己的返回值有关,也与dispatchTouchEvent有关。
我们可以看一下源码:
而在这个判断条件中dispatchTransformedTouchEvent中返回的与child.dispatchTouchEvent(event)有关。
所以这里的逻辑就会变得较为耦合。感兴趣的朋友可以深入看一下,我这里直接说结论吧。
如果没有重写dispatchTouchEvent,那么onTouchEvent返回true阻断,或返回false继续上传没有问题。
如果重写了dispatchTouchEvent,并且强制了返回值,那么是否上报与dispatchTouchEvent的返回值有关,与onTouchEvent无关。
结论
1. 事件的分发顺序是自上而下的(从爸爸到儿子),而处理的顺序是从下而上的(从儿子到爸爸)
2. onInterceptTouchEvent的返回值表示当前空间是否阻断分发,如果是,不再向儿子传递,交由自己的onTouchEvent处理
3. dispatchTouchEvent强制返回false时,不会在处理后续事件,如up或者move。同时dispatchTouchEvent与onTouchEvent的上传有关,所以不建议重写该方法,处理不当,会导致onTouchEvent的上传混乱
4. onTouchEvent返回为true的时候,表示该控件已经消费了touch事件,不用告诉爸爸了(父控件),返回false,会继续上传给父控件。
感兴趣的朋友可以根据demo自己修改返回值试一下。demo地址点击阅读全文。
• end •
作者 | mymdeep
领取专属 10元无门槛券
私享最新 技术干货