下面源码基于Android11 API30

文章里会用到上篇文章的布局例子这里先做一个初始化 ,让他们都返回默认值 

 

重写方法dispatchTouchEvent

重写onTouchEvent

isClickable

MyLinearLayoutOut

return super.dispatchTouchEvent(ev);

return super.onTouchEvent(event);

 

MyLinearLayout

return super.dispatchTouchEvent(ev);

return super.onTouchEvent(event);

 

MyButton

return super.dispatchTouchEvent(ev);

return super.onTouchEvent(event);

手动置为false

 

1 Activity在事件传递的特殊位置

Activity对于 dispatchTouchEvent 和 onTouchEvent分为两种情况

1 有控件接收处理事件,它和其它布局差不多,dispatchTouchEvent从它开始,返回被处理事件的控件拦截它也被拦截。

2 没有控件处理事件的的时候,这时候就有点不一样了,我们来看下源码

public class Activity extends ContextThemeWrapper
        implements Window.Callback,...... {     
 
        ......省略其它代码......
 
  
   
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             
            onUserInteraction(); 
        }
        ✍这里没有子控件接收处理事件,getWindow().superDispatchTouchEvent(ev)
           最终返回值是false
        if (getWindow().superDispatchTouchEvent(ev)) {
           
            return true;
        }
        ✍ 这个就会走到这里,调用它的onTouchEvent方法
        return onTouchEvent(ev);
    }
  
 
    public void onUserInteraction() {
        
    }
 
}

所以没有控件接收处理事件,对于UP动作Activity依旧会执行!

还是用上一篇文章的例子,只不过用文章最开始的把 MyLinearLayout 里重写的 onTouchEvent 返回值改为默认 return super.onTouchEvent(event); MyButton还是设置 android:clickable="false",这样就没有任何控件接收处理事件了,再点击MyButton,日志如下:

2021-03-23 15:47:11.260 I/lyw:  MainActivity      dispatchTouchEvent Down
2021-03-23 15:47:11.261 I/lyw:  MyLinearLayoutOut dispatchTouchEvent ACTION_DOWN
2021-03-23 15:47:11.261 I/lyw:  MyLinearLayout    dispatchTouchEvent ACTION_DOWN
2021-03-23 15:47:11.261 I/lyw:  MyButton          dispatchTouchEvent ACTION_DOWN

2021-03-23 15:47:11.261 I/lyw:  MyButton          onTouchEvent ACTION_DOWN
2021-03-23 15:47:11.261 I/lyw:  MyLinearLayout    onTouchEvent ACTION_DOWN
2021-03-23 15:47:11.261 I/lyw:  MyLinearLayoutOut onTouchEvent ACTION_DOWN
2021-03-23 15:47:11.261 I/lyw:  MainActivity      onTouchEvent Down

2021-03-23 15:47:11.363 I/lyw:  MainActivity      dispatchTouchEvent Up
2021-03-23 15:47:11.364 I/lyw:  MainActivity      onTouchEvent Up

2 OnTouch、OnTouchEvent 、 OnClickListener 执行顺序

在文章2 中我们也分析过了先判断OnTouch 再判断OnTouchEvent 最后判断 OnClickListener,这就是这三者的执行先后顺序。

3 重写dispatchTouchEvent 拦截事件的问题

3.1 文章最开始的表格初始化内容开始修改,把MyLinearLayout重写的dispatchTouchEvent方法改为 return true。

2021-03-23 15:54:42.610 I/lyw:  MainActivity      dispatchTouchEvent Down
2021-03-23 15:54:42.610 I/lyw:  MyLinearLayoutOut dispatchTouchEvent ACTION_DOWN
2021-03-23 15:54:42.610 I/lyw:  MyLinearLayout    dispatchTouchEvent ACTION_DOWN

2021-03-23 15:54:42.720 I/lyw:  MainActivity      dispatchTouchEvent Up
2021-03-23 15:54:42.720 I/lyw:  MyLinearLayoutOut dispatchTouchEvent ACTION_UP
2021-03-23 15:54:42.720 I/lyw:  MyLinearLayout    dispatchTouchEvent ACTION_UP

看到日志人都懵了,咋回事,想象中dispatchTouchEvent返回值return true;事件应该交给MyLinearLayout的onTouchEvent来处理啊。怎么没有一个onTouchEvent被调用了?

在MyLiearLayoutOut的dispatchTouchEvent方法中调用到
 ✍ 🔺 嵌套循环起源 
  if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
        ✍ 这个child就是MyLiearLayout
  }
 然后在dispatchTransformedTouchEvent中
 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {


                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
          
                  ✍child为MyLiearLayout,而我们重写了它的dispatchTouchEvent
                     就不会再调用它的父类ViewGroup的dispatchTouchEvent
                     而是调用我们重写的方法return true;
                    handled = child.dispatchTouchEvent(event);
 
                    
                }


}
然后回到 MyLiearLayoutOut 嵌套循环起源 那里为true,进入判断条件,
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
再往后就走到
 if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
 } else {
      if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                handled = true;
       } else { 
         ......        
       }
}
 return handled;
就结束到上一层布局,也是一样逻辑,逐层向外就没有一层会调用onTouchEvent。
仔细想一样返回值一般是由View的dispatchTouchEvent开始逐个验证onTouchListener,onTouchEvent,onClicklistener三个条件,而我们直接重写返回true,好像会让外层布局
以为已经有控件消费掉了就不用了验证自己了,只要把消费事件的child保存下来,用来传递
后续UP动作就可以了。

3.2 那我们把MyLinearLayout重写dispatchTouchEvent 改成return false;是什么情况:

2021-03-23 15:59:17.840 I/lyw:  MainActivity      dispatchTouchEvent Down
2021-03-23 15:59:17.840 I/lyw:  MyLinearLayoutOut dispatchTouchEvent ACTION_DOWN
2021-03-23 15:59:17.840 I/lyw:  MyLinearLayout    dispatchTouchEvent ACTION_DOWN

2021-03-23 15:59:17.840 I/lyw:  MyLinearLayoutOut onTouchEvent ACTION_DOWN
2021-03-23 15:59:17.840 I/lyw:  MainActivity      onTouchEvent Down

2021-03-23 15:59:17.951 I/lyw:  MainActivity      dispatchTouchEvent Up
2021-03-23 15:59:17.951 I/lyw:  MainActivity      onTouchEvent Up

事件依旧被拦截了,只不过因为return false,所以它的外层开始验证自身是否消费该事件:

在MyLiearLayoutOut的dispatchTouchEvent方法中调用到
 ✍ 🔺 嵌套循环起源 
  if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
        ✍ 这个child就是MyLiearLayout
  }
 然后在dispatchTransformedTouchEvent中
 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {


                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
          
                  ✍child为MyLiearLayout,而我们重写了它的dispatchTouchEvent
                     return false;
                    handled = child.dispatchTouchEvent(event);
 
                    
                }


}
然后回到 MyLiearLayoutOut 嵌套循环起源 那里为false,没有进入判断条件mFirstTouchTarget == null
再往后就走到
  if (mFirstTouchTarget == null) {
         ✍走这里验证自身是否处理该事件      
         handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
 } else {
      
         ......        
        
}
return handled;
 
最终没有任何控件接收处理事件,UP事件只有Activity被调用了。

小结:

我们重写 dispatchTouchEvent 无论 return true 或者 return false,事件都会被拦截了,不再向内层传递,而且也不会回调该控件的onTouchEvent方法。

那我们想在某控件拦截事件并交给它处理咋办?有专门的 onInterceptTouchEvent 方法,重写该方法返回true就可以了,然后会验证该控件是不是要接收消费事件。

那要不要重写dispatchTouchEvent 方法呢? 弄明白原理可能在某种场景下有奇效。。。?