参考: https://www.jianshu.com/p/7d50a6b0b6af
拦截顺序:
activity的dispatch-->父容器dispatch--->父容器intercept--->子容器的ontouch(一般return 值为true)--->父容器的ontouch-->activity的onTouch
如果中间被拦截,就交由该层级的onTouch事件处理
view中的监听事件优先级:OnTouchListener---->OnTouchEvent---->OnCLickListener
onTouchOListener不重写,默认返回false(重写为true则不会将事件交给touchevent),事件依旧交给onTouchEvent处理,如果onTouchEvent中的down和up事件都返回的是super.onTouchEvent,则能够触发onClickListener中的click事件
如果某个子view只在onTouchEvent中的down事件返回false,其他都是true,那么这个事件将会给父容器onTouch来处理,且相当于这个事件的down move up都会给父容器,子容器由于没有接收到down事件 后续一系列事件都不会给子容器实现;
相反的,如果这个子view在onTOuchEvent中的down事件返回true,而其他都是false,那么这个事件down move up依旧还是给子容器处理,case move up代码中都能够走到
注意:requestDisallowInterceptTouchEvent只针对于父容器intercept方法中down必须为false才能影响父容器的拦截,如果为true,那么整个系列的事件都由父容器完成了 不会再交给子容器来做;如果父容器想保留down move up事件,在intercept方法中的move 和 up返回true即可,但是这样 子view就拿不到move和up事件 将无法完成监听点击等事件;因此down move和up需要在父容器intercept dispatch中和子view的onTouch或者dispatch中进行取舍, 但是最重要的是父容器的down一定不能为true,要不然子view无论怎么拦截都拿不到后续事件了。
总结一下:
子View中的监听事件优先级: OnTouchListener---->OnTouchEvent---->OnCLickListener
如果优先级高的拦下事件,就不会再传递给后面的监听事件
1.Touch事件分发中只有两个主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三个相关事件。View包含dispatchTouchEvent、onTouchEvent两个相关事件。其中ViewGroup又继承于View。
2.ViewGroup和View组成了一个树状结构,根节点为Activity内部包含的一个ViwGroup。
3.触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down和Up都只有一个,Move有若干个,可以为0个。
4.当Acitivty接收到Touch事件时,将遍历子View进行Down事件的分发。ViewGroup的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在onTouchuEvent结果返回true。
5.当某个子View返回true时,会中止Down事件的分发,同时在ViewGroup中记录该子View。接下去的Move和Up事件将由该子View直接进行处理。由于子View是保存在ViewGroup中的,多层ViewGroup的节点结构时,上级ViewGroup保存的会是真实处理事件的View所在的ViewGroup对象:如ViewGroup0-ViewGroup1-TextView的结构中,TextView返回了true,它将被保存在ViewGroup1中,而ViewGroup1也会返回true,被保存在ViewGroup0中。当Move和UP事件来时,会先从ViewGroup0传递至ViewGroup1,再由ViewGroup1传递至TextView。
6.当ViewGroup中所有子View都不捕获Down事件时,将触发ViewGroup自身的onTouch事件。触发的方式是调用super.dispatchTouchEvent函数,即父类View的dispatchTouchEvent方法。在所有子View都不处理的情况下,触发Acitivity的onTouchEvent方法。
7.onInterceptTouchEvent有两个作用:1.拦截Down事件的分发。2.中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。
ViewGroup的相关事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。View的相关事件只有两个:dispatchTouchEvent、onTouchEvent。
先分析ViewGroup的处理流程:首先得有个结构模型概念:ViewGroup和View组成了一棵树形结构,最顶层为Activity的ViewGroup,下面有若干的ViewGroup节点,每个节点之下又有若干的ViewGroup节点或者View节点,依次类推。如图:
当一个Touch事件(触摸事件为例)到达根节点,即Acitivty的ViewGroup时,它会依次下发,下发的过程是调用子View(ViewGroup)的dispatchTouchEvent方法实现的。简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。上述例子中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件。
ViewGroup的onTouchEvent事件是什么时候处理的呢?当ViewGroup所有的子View都返回false时,onTouchEvent事件便会执行。
滑动冲突解决:
思路一:
父容器中事件拦截onInterceptTouchEvent的逻辑如以下代码所示:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercept = false;
switch(ev.getAction()){
case MotionEvent.ACTION_DOWN:
intercept = false;
break;
case MotionEvent.ACTION_MOVE:
if(满足我们需要的条件){
intercept = true;
}else{
intercept = false;
}
break;
case MotionEvent.ACTION_UP:
intercept = false;
break;
default:
break;
}
return intercept;
}
其中ACTION_DOWN事件不能进行拦截,因为在之前分发机制的分析时我们发现,如果拦截了ACTION_DOWN事件,那么之后的所有事件都会交给拦截的view进行处理。这里UP事件返回false是因为,如果不让子View拿到Up事件,那么子view的点击事件也将无法完成,这将对按钮等空间(不需要点击的控件可返回true)会产生巨大影响。
思路二:
(2)利用子View的requestDisallowInterceptTouchEvent(true)方法
这个思路就是父布局默认拦截除了ACTION_DOWN的所有事件,子View中在dispatchTouchEvent方法中根据需要来干预父布局的拦截策略。默认不允许父布局拦截事件,在需要父布局处理事件时,通过requestDisallowInterceptTouchEvent(false)方法让父布局处理事件,其他时候都由子View处理。
注意点:
- 同样的对于ACTION_DOWN事件,onInterceptTouchEvent方法必须返回false,其他事件默认返回true
- 在子View的dispatchTouchEvent方法中,对于ACTION_DOWN事件,通过调用requestDisallowInterceptTouchEvent(true)默认不允许父布局拦截事件,这样后续事件都交给子View处理
- 在子View的dispatchTouchEvent方法中,对于ACTION_MOVE事件,默认是子View处理,在需要父布局处理时,调用requestDisallowInterceptTouchEvent(false)方法来让父布局拦截事件,交给父布局处理。
子view:
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
ev?.run {
when(action){
MotionEvent.ACTION_DOWN -> parent.requestDisallowInterceptTouchEvent(true)
MotionEvent.ACTION_MOVE ->{
if(满足需要让外部容器拦截事件){
parent.requestDisallowInterceptTouchEvent(false)
}
}
}
}
return super.dispatchTouchEvent(ev)
}
这里需要提一点,retuan true 是告诉系统当前的View需要处理这次的touch事件,以后的系统发出的ACTION_MOVE,ACTION_UP还是需要继续监听并接收的,而且这次的action已经被这个View给消费掉了,父层的ViewGroup将不会走onTouchEvent了。所以每一个action最多只能有一个onTouchEvent接口返回true。如果return false,这个action会传向父级,调用父级View的onTouchEvent。但是这一次的touch事件之后发出的任何action,该View都不会再接受,onTouchEvent在这一次的touch事件中再也不会触发,也就是说一旦View返回false,那么之后的ACTION_MOVE,ACTION_UP等ACTION就不会在传入这个View,但是下一次touch事件的action还是会传进来的。所以在这边把返回值统一置为true,避免子View主动的将事件回传给父层ViewGroup。
onTouch和onTouchEvent、onLongClickListener和OnClickListener:
关于这几个概念的混淆:
这几个的前提是子View必须持有事件,父容器的所有拦截事件都没把事件给拦下来,最后传到子View,子View再传回去的路上才会发生的;
子View中的监听事件优先级: OnTouchListener---->OnTouchEvent---->OnLongClickListener(默认返回false)---->OnClickListener,如果从上往下有一层拦截了,后续就无法接受事件进行响应了;
onTouch和OnClick方法就是在子View在调用view.setOnTouchListener和view.setOnClickListener中重写的方法,onTouch方法也要返回一个Boolean值,和OnTouchEvent类似;
onClick和onLongclick的触发是在onTouchEvent回调中,如果onTouchEvent中不覆写super.onTouchEvent而直接return ture(虽然子view中的super.onTouchEvent 确实等于true),那么将不会触发这个view的longclick和click;如果只在onTouchEvent中的Down事件覆写super.onTouchEvent而UP事件直接return true那么将只触发onLongclick,因为onLongClick只关心接受到了Down事件,而Click还得关心有没有接收到UP事件;如果onLongClick中返回了true,那么将不会触发Click事件;
注!! onLongClick不是必定触发,得需要DOWN事件停留足够久才能触发
像下面这样:首先调用了setOnTouchListener方法,而且返回的是true:
那么打印出来的结果就是:
也就是说onClickListener根本不会被调用,如果自定义ViewC 还重写了OnTouchEvent事件,那么onTouchEvent事件也不会被调用;