在正常情况下,事件从Activity
的dispatchTouchEvent
传到PhoneWindow
的dispatchTouchEvent
再传到DecorView
的dispatchTouchEvent
然后再传到ViewGroup
的dispatchTouchEvent
,在ViewGroup
的dispatchTouchEvent
中,有两个子方法,一是onInterceptTouchEvent
,还有一个是onTouchEvent
。当onInterceptTouchEvent
返回true
的时候,代表拦截事件,则执行onTouchEvent
方法,否则不管返回啥,都传到下一层。ACTION_DOWN
事件在哪个控件消费了(return true
), 那么ACTION_MOVE
和ACTION_UP
就会从上往下(通过dispatchTouchEvent
)做事件分发往下传,就只会传到这个控件,不会继续往下传,如果ACTION_DOWN
事件是在dispatchTouchEvent
消费,那么事件到此为止停止传递,如果ACTION_DOWN
事件是在onTouchEvent
消费的,那么会把ACTION_MOVE
或ACTION_UP
事件传给该控件的onTouchEvent
处理并结束传递。
①ACTION_MOVE
会多次触发(体现为源码中同一块代码会调用多次)。
②ACTION_CANCLE
先记住,是事件被上层拦截时触发,至于具体的,后面就知道了。
知道了有什么事件,下面就得知道谁能怎么处理事件
触摸屏幕后,首先接触到事件的是Activity
,下面让我们从源码角度理解此流程
进入Activity
类的dispatchTouchEvent
方法
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
第一个if
不需要我们管,看第二个if
,它调用了PhoneWindow
的superDispatchTouchEvent
方法,让我们继续追踪
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
发现它调用了DecorView
的superDispatchTouchEvent
方法,我们继续追踪
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
DecorView
的super
是FrameLayout
,
但是由于FrameLayout
没有实现dispatchTouchEvent
方法,所以此方法最最终在ViewGroup
里面执行。我们追踪它,就进入了ViewGroup
的此方法
然后就开始事件分发了。事件分发在ViewGroup.java中。事件处理在View.java中,有两个方法,即dispatchTouchEvent和onTouchEvent。
那么就有人会问了,
View
的dispatchTouchEvent
和ViewGroup
的dispatchTouchEvent
有什么区别呢?其实是这样的,我们知道,ViewGroup
是继承View
的,所以它重写了View
的dispatchTouchEvent
方法,使得它具有事件分发功能。而具体如何分发,后面来分析。
插播一个小知识:什么是冲突?事件只有一个,但是有多个人想要处理,如果处理的人不是我们想要的那个人,我们就说发生了冲突
来看这样一个非常典型的例子:
比如这个例子,我们想让onTouch
处理,结果onClick
处理了,那么我们就说发生了冲突。经过测试我发现,当我在onTouch
方法中返回true
的时候,onClick
方法不执行,当返回false
的时候,onClick
方法执行。此时问题来了,什么时候onTouch
处理,什么时候onClick
处理呢?我们能否自己控制呢?
刚刚说了,事件处理在View
的dispatchTouchEvent
中,所以我们直接进入源码找答案
public boolean dispatchTouchEvent(MotionEvent event) {
....
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
....
return result;
}
可以通过源码发现,在第一个if
的最后一个条件之前,所有的条件都为true
。而我们的onTouch
方法就是重写了最后一个条件的那个语句,即li.mOnTouchListener.onTouch(this, event)
,当我们onTouch
返回true
的时候,result
赋值为true
,当返回false
的时候,result
也赋值为false
。然后我们看下一个if
。当是第一种情况的时候,result
为true
,!
之后为false
,所以onTouchEvent
方法不会执行。而当是第二种情况的时候,result
为false
,!
之后为true
,所以onTouchEvent
就会执行。这个和前面的测试结果好像很像,所以我们不妨做个大胆的推测,即onClick
方法就在onTouchEvent
方法中调用。口说无凭,我们追踪进入
public boolean onTouchEvent(MotionEvent event) {
...
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
if (!post(mPerformClick)) {
performClickInternal();
}
...
我们知道onClick
方法是在ACTION_UP
的时候执行的,所以我们进入相应的case
语句,找到performClickInternal
方法(略去大量无关代码),追踪
private boolean performClickInternal() {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick();
return performClick();
}
追踪performClick
public boolean performClick() {
...
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
...
}
终于在if
中找到了onClick
的调用。同时result
为true
,意为告诉父容器,此事件我已处理了。
刚刚我们通过一个小案例,一个小事件冲突,体会到了View
事件处理的机制以及流程,也就是说View
的事件处理就是onTouch
和onClick
。除此之外,我们也体会到了一个小的事件冲突的处理是怎样的。也就是说,这张图的红框部分我们已经解决了,接下来我们看ViewGroup
的分发事件,是如何处理的
DecorView
其实就是个ViewGroup
。下面这些总监也是各种ViewGroup
。我们的事件分发其实就是在总经理这里开始分发,一层一层地来。
第一个层级:总经理,即ViewGroup
第二个层级:总监,也是ViewGroup
第三个层级:比如清洁工,有可能是View
,也可能是ViewGroup
比如这张图,如果清洁是ImageButton
那就是View
,如果它是清洁工的头,比如LinearLayout
,那么他就是ViewGroup
。但是我们这里为了方便理解清楚,默认规定为普通清洁工,即View
(其实都一样)。
我们首先看总经理,也就是ViewGroup
的事件分发。源码进入ViewGroup.java
,找到dispatchTouchEvent
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
//-----------------------------------
//-----------------------------------
//如果是DOWN事件,则进行一些清0操作
//-----------------------------------
//-----------------------------------
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//-----------------------------------
//-----------------------------------
//下面办的事就是判断事件是否需要拦截
//-----------------------------------
//-----------------------------------
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
...
//-----------------------------------
//-----------------------------------
//如果拦截了,就会走到这个if里面
//-----------------------------------
//-----------------------------------
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
。。。
}
首先如果判断是否是DOWN
事件,如果是则进行一些清0操作(我们本来就是分析的DOWN
事件,所以此条件一定成立),然后判断事件是否需要拦截。
我们这里是需要拦截的情况,事件拦截了,也就是说onInterceptTouchEvent
方法的返回值为true
,那么相当于拦截的这个ViewGroup
是最后一个,就得面临一个选择,即事件到底处不处理,然后走入if (mFirstTouchTarget == null)
中,执行dispatchTransformedTouchEvent
方法,这个方法,就是是否处理。追踪进入。我们结合前面的可以知道,他这里child
的参数为null
,说明它这里不是让孩子处理的,而是给自己处理的。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
...
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
...
省略无关代码,发现会执行super.dispatchTouchEvent
,这里就到View
里面去了。所以ViewGroup
的处理事件,就是交给View
的。就又看它是onTouch
处理还是onClick
处理,就是刚刚那个案例。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//-----------------------------------
//-----------------------------------
//判断是否拦截,假设这里是不拦截
//-----------------------------------
//-----------------------------------
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
...
if (!canceled && !intercepted) {
//-----------------------------------
//-----------------------------------
//如果没有拦截,就会走到这个if里面,而不走上面说的那个if里面
//-----------------------------------
//-----------------------------------
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//-----------------------------------
//-----------------------------------
//进来之后首先会判断一下,如果为down事件才进行分发
//-----------------------------------
//-----------------------------------
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
...
//-----------------------------------
//-----------------------------------
//newTouchTarget一定为null,而第二个条件,就是说
//比如总经理,它的childrenCount就是那5个总监的5,不包括下面的子View
//满足条件,进入此if
//-----------------------------------
//-----------------------------------
if (newTouchTarget == null && childrenCount != 0) {
...
//-----------------------------------
//-----------------------------------
//进入buildTouchDispatchChildList,进行一个排序
//-----------------------------------
//-----------------------------------
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//-----------------------------------
//-----------------------------------
//倒序取,取出各种总监,假如取出来的是市场总监
//-----------------------------------
//-----------------------------------
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//-----------------------------------
//-----------------------------------
//会进行一个判断,虽然市场总监是总监,但是到底有没有能力呢?
//所以会进行两个判断
//1.第一个是判断View能否接收事件,有两个条件,
//第一个是View是不是显示的即VISIBLE
//第二个是,如果不是VISIBLE,那就是有可能是动画,即animation,不是VISIBLE,有动画也行
//2.第二个条件是,point要在View的范围内
//如果市场总监没有这个能力,就分到下一个总监即continue
//-----------------------------------
//-----------------------------------
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
...
//-----------------------------------
//-----------------------------------
//又看到了dispatchTransformedTouchEvent分发处理事件方法,但是参数不同
//它的第三个参数不是null,而是child,就是现在的市场总监,对市场总监进行事件分发
//-----------------------------------
//-----------------------------------
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//-----------------------------------
//-----------------------------------
//有可能dispatchTransformedTouchEvent返回false,也就是说
//市场总监不处理,那么就不会进入if,就开始下一个for循环。
//对应上面例图就是说总经理问完市场总监,市场总监不行,那就去问工程总监
//-----------------------------------
//-----------------------------------
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
//-----------------------------
//-----------------------------
//如果事件要处理的话,那么就会进行一些赋值。比如说
//newTouchTarget = mFirstTouchTarget != null
//target.next = null
//alreadyDispatchedToNewTouchTarget = true;
//-----------------------------
//-----------------------------
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;//直接结束for循环,后续就不该工程总监或者其他总监的事情了
}
...
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//-----------------------------------
//-----------------------------------
//mFirstTouchTarget不为null,所以走else。这个while循环只循环一次。
//因为循环一次后target就为null了
//-----------------------------------
//-----------------------------------
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
//-----------------------------------
//-----------------------------------
//这里if语句条件满足,所以进入这里
//-----------------------------------
//-----------------------------------
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
//因为市场总监已经处理了,所以总经理就不处理了,所以handled赋值为true
} else {
...
这里要纠正我们之前一个不太确切的观点:如果连DOWN事件都拿不到,那么MOVE和UP事件也拿不到。其实这是针对叶子节点的,也就是比如上面例图中的高空清洁,但是如果它是总监的话,就不同了。它没有处理DOWN事件,也可能处理MOVE事件。
首先判断是否需要拦截,这里是不拦截的情况。然后进入 if (!canceled && !intercepted)
然后判断,如果是DOWN
才能进行事件分发。然后到了第二个if,if (newTouchTarget == null && childrenCount != 0)
。newTouchTarget
一定为null
,而第二个条件,就是说比如总经理,它的childrenCount
就是那5个总监的5,不包括下面的子View
。满足条件,进入此if,然后得到一个排好序的preorderedList
,怎么排的序呢?
追踪buildTouchDispatchChildList
public ArrayList<View> buildTouchDispatchChildList() {
return buildOrderedChildList();
}
追踪buildOrderedChildList
ArrayList<View> buildOrderedChildList() {
final int childrenCount = mChildrenCount;
if (childrenCount <= 1 || !hasChildWithZ()) return null;
if (mPreSortedChildren == null) {
mPreSortedChildren = new ArrayList<>(childrenCount);
} else {
// callers should clear, so clear shouldn't be necessary, but for safety...
mPreSortedChildren.clear();
mPreSortedChildren.ensureCapacity(childrenCount);
}
final boolean customOrder = isChildrenDrawingOrderEnabled();
for (int i = 0; i < childrenCount; i++) {
// add next child (in child order) to end of list
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View nextChild = mChildren[childIndex];
final float currentZ = nextChild.getZ();
// insert ahead of any Views with greater Z
int insertIndex = i;
while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
insertIndex--;
}
mPreSortedChildren.add(insertIndex, nextChild);
}
return mPreSortedChildren;
}
之后返回已经排好序的数组mPreSortedChildren
。
排序是根据Z值来排的。这个Z值一般用不到。所以如果不写Z值,那么就默认根据你写的顺序来排序。比如FrameLayout
包括ViewPager
和TextView
,那么ViewPager
就排在TextView
的前面,以此类推。越是往前的,它就优先接收事件。
这里有意思的是它是根据一个倒序来取东西,也就是说如果想让谁先处理事件,就把他放在数组的后面。
然后我们再回到上面,preorderedList
接收了排好序的数组。然后倒序取,然后会进行一个
if (!child.canReceivePointerEvents()|| !isTransformedTouchPointInView(x, y, child, null))
,判断子view
即市场总监有没有能力处理事件
追踪canReceivePointerEvents
protected boolean canReceivePointerEvents() {
return (mViewFlags & VISIBILITY_MASK) == VISIBLE || getAnimation() != null;
}
1.第一个是判断
View
能否接收事件,有两个条件,
第一个是View
是不是显式的即VISIBLE
第二个是,如果不是VISIBLE
,那就是有可能是动画,即animation
,不是VISIBLE
,有动画也行
2.第二个条件是,point
要在View
的范围内
如果市场总监没有这个能力,就分到下一个总监即continue
又看到了dispatchTransformedTouchEvent
分发处理事件方法,但是参数不同了。它的第三个参数不是null
,而是child
,就是现在的市场总监,(因为我们一开始是面对的总经理,所以它的第一个孩子是市场总监)
我们进入dispatchTransformedTouchEvent
看看
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
...
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
//-----------------------------------
//-----------------------------------
//因为child不为null,所以就进入到else里面
//-----------------------------------
//-----------------------------------
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
//-----------------------------------
//-----------------------------------
//这里的dispatchTouchEvent,一定会走ViewGroup的dispatchTouchEvent,而不是View的。
//相当于是一个递归,又是开始之前的情况,首先判断是否拦截,如果拦截怎么怎么样,如果不拦截怎么怎么样
//-----------------------------------
//-----------------------------------
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
对市场总监进行事件分发。如果事件要处理的话,那么就会进行一些赋值。比如说
newTouchTarget = mFirstTouchTarget != null
target.next = null
alreadyDispatchedToNewTouchTarget = true
然后一个break
,直接结束for
循环,后续就不该工程总监或者其他总监的事情了
然后走下面的else
,mFirstTouchTarget
不为null
,所以走else
。这个while
循环只循环一次。因为循环一次后target
就为null
了
然后走if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget)
。因为市场总监已经处理了,所以总经理就不处理了,所以handled
赋值为true
,意为已经处理了。
刚刚是市场总监处理的情况,但是有可能dispatchTransformedTouchEvent
返回false
,也就是说市场总监不处理,那么就不会进入if
,就开始下一个for
循环。
对应上面例图就是说总经理问完市场总监,市场总监不行,那就去问工程总监。
在上面的分析之后,我们知道了在DOWN
之后的事件分发流程,走完这个流程后,我们就能确定,这个事件到底是谁来处理。也就是说DOWN事件处理完之后就能确定到底是谁来处理此事件。这就是View
的事件处理以及ViewGroup
的事件分发流程源码解析,后面还有与MOVE
相关的知识。
当MOVE
事件来了后,还是要先分析总经理,从总经理开始,而不是直接分析DOWN
事件选择的处理事件的某位总监。比如刚刚DOWN
事件决定市场研发来处理事件,那么MOVE
事件来了之后,首先分析总经理,然后分析市场总监,然后是市场研发。
首先还是要走ViewGroup
的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
...
//--------------------------
//--------------------------
//因为是MOVE事件,所以这里不进去
//--------------------------
//--------------------------
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//--------------------------
//--------------------------
//虽然是MOVE事件,但是这里mFirstTouchTarget 不为null,所以还是会进入if
//--------------------------
//--------------------------
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
//--------------------------
//--------------------------
//没有拦截,所以这里为false
//--------------------------
//--------------------------
intercepted = false;
}
}
...
//--------------------------
//--------------------------
//会进入此if
//--------------------------
//--------------------------
if (!canceled && !intercepted) {
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//--------------------------
//--------------------------
//因为是MOVE事件,所以不会进入此if
//又因为此if是用来分发事件的,所以MOVE事件不会分发事件
//--------------------------
//--------------------------
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
...
//--------------------------
//--------------------------
//因为不为null,所以它不走if,去else
//--------------------------
//--------------------------
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;
//--------------------------
//--------------------------
//alreadyDispatchedToNewTouchTarget 在这里之前被赋值为false了,所以这里进入false
//--------------------------
//--------------------------
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//--------------------------
//--------------------------
//这里cancelChild 被赋值为false
//--------------------------
//--------------------------
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//--------------------------
//--------------------------
//分发给市场总监,让我们追踪进入
//--------------------------
//--------------------------
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//-----------------------------------------------------------------------------------
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
...
//--------------------------
//--------------------------
//发现它这里又调用了ViewGroup的dispatchTouchEvent方法,也就是市场总监再进行一个分发
//比如分发给第一个子View,即市场研发,再走到这里,就调用View的dispatchTouchEvent方法
//调用View的dispatchTouchEvent方法就是处理事件了(最开始中有说到)
//--------------------------
//--------------------------
handled = child.dispatchTouchEvent(transformedEvent);
}
transformedEvent.recycle();
return handled;
}
//-----------------------------------------------------------------------------------
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;
}
也就是说市场总监和总经理的流程是一样的
MOVE
不进行事件分发,是说,在事件分发的时候,正常情况下都会去问,比如总经理先问市场总监,然后如果他不干就去问下一个总监,这是正常情况下的事件分发。而MOVE事件就不一样了,它是直接找到市场总监,然后直接找到市场研发。也就是找到DOWN决定的事件处理对象只能在MOVE
事件中处理事件冲突。因为在DOWN
中进行事件分发。而且父容器可以抢子View
的事件,而子View
不可以抢父容器的事件。子View
一旦拿到了事件,那么事件再由谁处理就是子View说了算的。
先看ViewGroup
的dispatchTouchEvent
中
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
如果onInterceptTouchEvent
返回为true
,那么intercepted
就为true
,然后看往下看
if (!canceled && !intercepted) {
这个if
就进不去了,而这个if
就是管事件分发的,所以子View
不会知道事件,因为事件被拦截了。那么有什么方法,可以不让父容器这么“独裁”吗?答案是肯定的。我们看
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
在intercepted
被赋值之前,会有一个判断,也就是说如果disallowIntercept
为true
,那么不管你onInterceptTouchEvent
是true
还是false
,intercepted
都为false
。所以事件就不会被拦截。这就是处理时间冲突的第一个思路,就是子View
通过控制disallowIntercept
来不让父容器拦截事件。
那么问题来了,通过什么方式能控制它的值呢?其实谷歌给我们提供了一个方法(我们有时也称为子View
的尚方宝剑),即
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
如果disallowIntercept
为true
,然后 mGroupFlags |= FLAG_DISALLOW_INTERCEPT再& FLAG_DISALLOW_INTERCEPT != 0
的结果绝对为true
,那么这里就不会进入if
。
所以就可以看到我们下面这一张图片的代码①
。假设子View
是ListView
,父容器是ViewPager
。一开始是DOWN
的时候,我们不能让父容器拦截事件,要掌握在我们自己手里,一开始也说了,子View
一旦拿到了事件,那么事件再由谁处理就是子View
说了算的。然后MOVE
的时候我们进行一个判断,如果X方向
的移动距离大,那么就还是让给父容器处理,如果X
方向的移动距离不如Y
方向的,那么还是我这个子控件ListView
处理。从这里也可以看出来,处理权就掌握在子View
手中了
但是关注了这些还是不够,我们看源码,在赋值disallowIntercept
之前,有一个重置处理,我在前面也提到过
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
我们追踪resetTouchState
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
会发现它对mGroupFlags
进行了一个赋值。这就导致了DOWN
事件的时候,disallowIntercept
就为false
,因为mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT & FLAG_DISALLOW_INTERCEPT != 0
的结果绝对为fal
se。然后又进入了if
语句,所以我们就白搞了。所以为了解决这个问题,需要父容器做一些处理
,即在ViewPager
中添加此方法
也就是说如果是DOWN
事件,我就返回false
,不管你重置与否,最后intercepted
都为false
,这样就不拦截了。
以上都是讲的怎么不让父容器拦截以及相应流程,那么如果在这张图中,走了②,也就是说让父容器拦截,那么它的流程是怎样的呢
这个父容器拦截,也是子控件让给父容器的,而不是父容器一开始就有的
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
//----------------------
//----------------------
//这里intercepted 赋值为true,也就是拦截
//----------------------
//----------------------
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
//下面这个if不走,也就是不进行分发
if (!canceled && !intercepted) {
....
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//----------------------
//----------------------
//要走这个else
//----------------------
//----------------------
...
else {
//----------------------
//----------------------
//注意这里,关键点来了。这里因为intercepted为true,所以cancelchild为true,所以这里
//----------------------
//----------------------
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//----------------------
//----------------------
//然后追踪dispatchTransformedTouchEvent方法
//----------------------
//----------------------
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//-------------------------------------------------------------------------------
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
//----------------------
//----------------------
//先保存旧动作,然后因为cancle为true,所以进入if
//----------------------
//----------------------
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
//----------------------
//----------------------
//如果我们的研究对象是总经理,那么child就是市场总监等
//如果研究对象是市场总监等,那么child就是比如市场研发等
//然后置为cancel
//----------------------
//----------------------
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
//----------------------
//----------------------
//然后调用child的dispatchTouchEvent。相当于刚刚例子中的listview调用了cancel事件
//也就是说它自己取消了这个事件,也就是本文中最最开始说的,cancel是上层拦截的时候触发,答案就在这里
//----------------------
//----------------------
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
...
//-------------------------------------------------------------------------------
if (cancelChild) {
if (predecessor == null) {
//----------------------
//----------------------
//然后这里将mFirstTouchTarget 赋值为next,也就是null
//----------------------
//----------------------
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
...
然后第一个MOVE
结束了,第一个MOVE
事件的目的是为了取消子控件处理事件(即取消ListView
处理事件,让它把事件让给父容器),并且把mFirstTouchTarget
赋值为null
。此时父容器即ViewPager
还没有响应事件。然后进行第二个MOVE
,我们分析父容器即ViewPager
是怎么把事件抢过来的,即怎么接过来的子控件让给它的事件。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
//----------------------
//----------------------
//因为是MOVE事件,而且mFirstTouchTarget 为null,所以这个if就不进去了,直接去else
//else把intercepted 赋值为了true
//----------------------
//----------------------
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
...
//----------------------
//----------------------
//这个if就不走了
//----------------------
//----------------------
if (!canceled && !intercepted) {
...
// Dispatch to touch targets.
//----------------------
//----------------------
//因为mFirstTouchTarget 为null,所以进入此if,然后就和DOWN事件拦截时的处理是一样的了
//----------------------
//----------------------
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
...
所以这也解释了为什么前面说
这里要纠正我们之前一个不太确切的观点:如果连DOWN事件都拿不到,那么MOVE和UP事件也拿不到。其实这是针对叶子节点的,也就是比如上面例图中的高空清洁,但是如果它是总监的话,就不同了。它没有处理DOWN事件,也可能处理MOVE事件。
看这里的父容器,它没有接收到DOWN
事件,但是抢到了MOVE
事件。
这里也解释了一个现象,就是我们平时使用手机的时候,上下滑动的同时进行左右滑动是可以的,而左右滑动的同时进行上下滑动是不行的。因为上下滑动可能是ListView
(不纠结是RecyclerView
还是ListView
,道理是一样的),是子View
,它可以掌握着事件的权力,而左右滑动是ViewPager
,它响应事件,是上下滑动即ListView
给的权力,是ListView
不要这个事件之后,它才响应的。
这个就很简单了
把
里面的全部注释掉,换成
这个很简单,当MOVE
事件的时候,如果是水平滑动,就拦截,即返回true
。如果是竖直滑动,就不拦截。
事件的分发流程是Activity-ViewGroup-View
消费流程正好反过来,是View-ViewGroup-Activity
down——一系列的move——up/cancel
如果View
的down
事件没有消费,那么后续的move
事件是没办法接收到的