1、现象分析
我们分别定义四个自定义view
// 如果是继承RelativeLayout则实现其三个方法,并输出log
// dispatchTouchEvent
// onInterceptTouchEvent
// onTouchEvent
public class MyRelaLayout1 extends RelativeLayout{}
public class MyRelaLayout2 extends RelativeLayout {}
// 如果是继承TextView则实现其两个方法,并输出log
// dispatchTouchEvent
// onTouchEvent
public class MyTextView1 extends TextView {}
public class MyTextView2 extends TextView {}
写一个相互嵌套的布局
<com.djk.test.touch.MyRelaLayout1
android:id="@+id/p_1"
android:layout_width="200dp"
android:layout_height="200dp">
<com.djk.test.touch.MyRelaLayout2
android:id="@+id/p_2"
android:layout_width="200dp"
android:layout_height="200dp">
<com.djk.test.touch.MyTextView1
android:id="@+id/c_1"
android:layout_width="150dp"
android:layout_height="150dp"
android:background="#000000" />
<com.djk.test.touch.MyTextView2
android:id="@+id/c_2"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#ff0000" />
</com.djk.test.touch.MyRelaLayout2>
</com.djk.test.touch.MyRelaLayout1>
实现效果如下图,MyTextView1为黑色背景,MyTextView2为红色背景,并覆盖在MyTextView1之上
下面我们来进行操作来分析touch事件的传递与拦截
现象一:就现在的默认情况下,我们点击红色区域
-> MyRelaLayout1.dispatchTouchEvent.Down -> MyRelaLayout1.onInterceptTouchEvent.Down
-> MyRelaLayout2.dispatchTouchEvent.Down -> MyRelaLayout2.onInterceptTouchEvent.Down
-> MyTextView2.dispatchTouchEvent.Down -> MyTextView2.onTouchEvent.Down
-> MyTextView1.dispatchTouchEvent.Down -> MyTextView1.onTouchEvent.Down
-> MyRelaLayout2.onTouchEvent.Down
-> MyRelaLayout1.onTouchEvent.Down
我们得到touch事件从MyRelaLayout1-> MyRelaLayout2-> MyTextView2-> MyTextView1
可以分析得出touch传递是从最外层的MyRelaLayout1传到第二层MyRelaLayout2
在RelativeLayout布局中,MyTextView2 和MyTextView1属于平等关系,那么最上层子view先拿到touch事件,然后再传递到下面的子view
现象二:我们给MyTextView2设置onTouchEvent事件return true
-> MyRelaLayout1.dispatchTouchEvent.Down -> MyRelaLayout1.onInterceptTouchEvent.Down
-> MyRelaLayout2.dispatchTouchEvent.Down -> MyRelaLayout2.onInterceptTouchEvent.Down
-> MyTextView2.dispatchTouchEvent.Down -> MyTextView2.onTouchEvent.Down
// ... Move
-> MyRelaLayout1.dispatchTouchEvent.Up -> MyRelaLayout1.onInterceptTouchEvent.Up
-> MyRelaLayout2.dispatchTouchEvent.Up -> MyRelaLayout2.onInterceptTouchEvent.Up
-> MyTextView2.dispatchTouchEvent.Up -> MyTextView2.onTouchEvent.Up
可以看出我们成功的把MyTextView1的所有事件都截断了
现象三:我们给MyRelaLayout2设置onInterceptTouchEvent事件return true
-> MyRelaLayout1.dispatchTouchEvent.Down -> MyRelaLayout1.onInterceptTouchEvent.Down
-> MyRelaLayout2.dispatchTouchEvent.Down -> MyRelaLayout2.onInterceptTouchEvent.Down -> MyRelaLayout2.onTouchEvent.Down
-> MyRelaLayout1.dispatchTouchEvent.Up -> MyRelaLayout1.onInterceptTouchEvent.Up
-> MyRelaLayout2.dispatchTouchEvent.Up -> MyRelaLayout2.onTouchEvent.Up
可以看出,成功将后面所有的事件都截断了,并调用了自己的onTouchEvent方法
2、源码分析(android-25)
public boolean dispatchTouchEvent(MotionEvent ev) {
// ...
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 清除touch targets 其核心代码就是 mFirstTouchTarget = null;
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 是否要拦截
final boolean intercepted;
// 如果是Down事件,或 mFirstTouchTarget != null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 子类请求不要拦截事件
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
// 子类没有请求不要拦截事件,走我们正常的流程
if (!disallowIntercept) {
// 调用自己的onInterceptTouchEvent方法来判断是否要拦截touch事件 默认情况下返回 fase
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}
// ...
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
// 没有取消 & 没有拦截事件 正常情况下if能够执行
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
// ...
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
// ...
// 获取其子view的集合
final View[] children = mChildren;
// 反序for循环,获取子view(这里就是RelativeLayout中多层覆盖的情况下,首先拿到最上层的子view)
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// ...
newTouchTarget = getTouchTarget(child);
// ...
// 详见下面的第二个代码块,如果dispatchTouchEvent方法返回true 则能进入这个方法
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// ...
// addTouchTarget() 方法核心代码 mFirstTouchTarget = target; 这里将 mFirstTouchTarget 赋值
// 然后将mFirstTouchTarget 的值再赋值给 newTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
if (preorderedList != null) preorderedList.clear();
}
}
}
// 这里如果mFirstTouchTarget = null,则去调用代码块2的方法,传入的child为null,这个时候就会调用自己的onTouchEvent方法
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
// 如果child 为null,则去调用父类的dispatchTouchEvent,ViewGroup的父类也是View,
// 所以相当于调用了View的dispatchTouchEvent,进而会调用自己的onTouchEvent方法
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {// 如果child 不为null,则去调用子类的dispatchTouchEvent,子类的touch事件从这里开始
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
return handled;
}
3、源码变通(android源码中,所有的touch事件都会调用dispatchTouchEvent方法,其实我们可以将它拆成Down事件和其他事件来分析)
第一步,当前是Down事件:
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
mFirstTouchTarget = null;
boolean intercepted = false;
// 调用自己的onInterceptTouchEvent方法来判断是否要拦截touch事件 默认情况下返回 false
intercepted = onInterceptTouchEvent(ev);
// 这里如果是不拦截touch事件
if (!intercepted) {
// 获取其子view的集合
final View[] children = mChildren;
// 反序for循环,获取子view(这里就是RelativeLayout中多层覆盖的情况下,首先拿到最上层的子view)
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = getChildView(i);
// 调用子类的dispatchTouchEvent,如果返回true则将 mFirstTouchTarget 赋值
handled = child.dispatchTouchEvent(event);
if (handled) {
// 这里将 mFirstTouchTarget 赋值
mFirstTouchTarget = target;
// 跳出循环,此后的view就收不到touch事件了
break;
}
}
}
if (mFirstTouchTarget == null) {
// 如果是mFirstTouchTarget == null,则说明没有一个子类返回true,
// 则直接调用View的dispatchTouchEvent,进而会调用自己的onTouchEvent方法
handled = super.dispatchTouchEvent(event);
}
return handled;
}
第二步,当前是 Move or Up 事件:
public boolean dispatchTouchEvent(MotionEvent ev) {
// ...
boolean handled = false;
// 是否要拦截
final boolean intercepted;
// 这里的 mFirstTouchTarget 是由Down事件记录的
if (mFirstTouchTarget != null) {
// 调用自己的onInterceptTouchEvent方法来判断是否要拦截touch事件 默认情况下返回 fase
intercepted = onInterceptTouchEvent(ev);
} else {// 如果Down事件记录的 mFirstTouchTarget == null; 则拦截判断直接置为 true
intercepted = true;
}
// 这里如果是不拦截touch事件
if (!intercepted) {
// 获取其子view的集合
final View[] children = mChildren;
// 反序for循环,获取子view(这里就是RelativeLayout中多层覆盖的情况下,首先拿到最上层的子view)
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = getChildView(i);
// 调用子类的dispatchTouchEvent,如果返回true则将 mFirstTouchTarget 赋值
handled = child.dispatchTouchEvent(event);
if (handled) {
// 这里将 mFirstTouchTarget 赋值
mFirstTouchTarget = target;
// 跳出循环,后面的view就收不到touch事件
break;
}
}
}
if (mFirstTouchTarget == null) {
// 如果是mFirstTouchTarget == null,则说明没有一个子类返回true,
// 则直接调用View的dispatchTouchEvent,进而会调用自己的onTouchEvent方法
handled = super.dispatchTouchEvent(event);
}
return handled;
}
4、下面来总结一下吧
①、ACTION_DOWN事件为一个事件序列的开始,中间有若干个ACTION_MOVE,最后以ACTION_UP结束。
②、一个clickable或者longClickable的View会永远消费Touch事件,不管他是enabled还是disabled的
③、Touch事件是从最顶层的View一直分发到手指touch的最里层的View,如果最里层View消费了ACTION_DOWN事件(设置onTouchListener,并且onTouch()返回true 或者onTouchEvent()方法返回true)才会触发ACTION_MOVE,ACTION_UP的发生,如果某个ViewGroup拦截了Touch事件,则Touch事件交给ViewGroup处理
④、如果某一个View拦截了事件,那么同一个事件序列的其他所有事件都会交由这个View处理,此时不再调用View(ViewGroup)的onIntercept()方法去询问是否要拦截了。