下面源码基于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 方法呢? 弄明白原理可能在某种场景下有奇效。。。?