touch事件的分发与处理

民间闲散程序员

公众号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

  • 发表于:
  • 原文链接:https://kuaibao.qq.com/s/20180716G07SPW00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券