事件分发机制并不是洪水猛兽

文章目录

  • OnTouchListener与OnClickListener的优先级
  • 事件分发
  • 滑动冲突
  • 处理滑动冲突
  • 1. 内部拦截法
    • 2.外部拦截法

当MotionEvent产生后,系统总归要将其传递到某个View,这个过程就是事件分发。事件分发机制离不开3个重要方法:dispatchTouchEvent(分发)、onInterceptTouchEvent(拦截,在dispatchTouchEvent中调用。需要注意,View中没有该方法)、onInterceptTouchEvent(处理事件,在dispatchTouchEvent中调用。)
这3个方法相爱相杀,紧密关联。ViewGroup收到点击事件后,它的dispatchTouchEvent方法首先会被调用。如果它的onInterceptTouchEvent方法返回了true,表示ViewGroup要将“快递”拦截下来,事件就成了“拦截件”。随后它的onTouchEvent会被调用来处理这件“快递”。如果ViewGroup的onInterceptTouchEvent返回了false,那么ViewGroup就不会拦截。快递就送到了View手里,View的dispatchTouchEvent就会被调用。如此循环往复,直到事件被处理。

OnTouchListener与OnClickListener的优先级

提到事件处理,当对一个Button同时设置了setOnTouchListener和setOnClickListener后,如果OnTouchListener的onTouch方法return 了false,那么两个方法都会执行。如果return了true,则只有onTouch被执行,onClick不会被执行。

现在通过源码分析原因:

View.java#dispatchTouchEvent()
...
if (onFilterTouchEventForSecurity(event)) {
    if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
        result = true;
    }
    //mListenerInfo是单例处理返回的对象
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnTouchListener != null
            && (mViewFlags & ENABLED_MASK) == ENABLED
            && li.mOnTouchListener.onTouch(this, event)) {
        result = true;
    }

    if (!result && onTouchEvent(event)) {
        result = true;
    }
}
...

这个mListenerInfo就是单例处理返回的一个ListenerInfo 对象,这就保证了mListenerInfo 肯定不为null,即li != null

ListenerInfo getListenerInfo() {
    if (mListenerInfo != null) {
        return mListenerInfo;
    }
    mListenerInfo = new ListenerInfo();
    return mListenerInfo;
}

它里面有着众多xxxListener,如:

public OnClickListener mOnClickListener;

protected OnLongClickListener mOnLongClickListener;

private OnTouchListener mOnTouchListener;

而我们setOnTouchListener时,就会将new出来的OnTouchListener 赋值过来,这就保证了li.mOnTouchListener != null

我们setOnTouchListener重写的onTouch方法的返回值起到了决定性的作用,它影响了if条件语句的执行。如果return true,那就所有的条件都满足,万事大吉,程序走进if里,result = true。如果重写的onTouch方法return了false,那么整个if条件语句就不满足,就不再会往里走了。

在仔细看if块:

if (!result && onTouchEvent(event)) {
        result = true;
    }

如果result为true的话,那么“&&”前面就是false,onTouchEvent(event)就没有再执行的必要了。onTouchEvent负责事件的消费,而onClick是在MotionEvent.ACTION_UP中被调用—>performClickInternal()—>performClick():

public boolean performClick() {
    // We still need to call this method to handle the cases where performClick() was called
    // externally, instead of through performClickInternal()
    notifyAutofillManagerOnClick();

    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        //这才是亮点!!!
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }

    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

    notifyEnterOrExitForAutoFillIfNeeded(true);

    return result;
}

这个if判断和刚才onTouch时的if是十分类似的。前面已经分析过了,if里面的条件都满足,于是就往里走,调用了 li.mOnClickListener.onClick(this)。至此,我们setOnClickListener就会响应点击事件了。

总结一下,TouchEvent发射出来一个信号,如果被onTouch拦截到了,自然就没onClick什么事了。可如果一旦onTouch没拦截到这个信号,onClick就会成功处理这个信号。

事件分发

事件产生后的传递顺序如下:
事件分发机制并不是洪水猛兽_第1张图片
来看ViewGroup对事件的分发过程,dispatchTouchEvent方法:

if (actionMasked == MotionEvent.ACTION_DOWN) {
    // 判断为DOWN事件,清除掉一些变量,重置状态
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}

// 没有拦截,为false
final boolean intercepted;
//当事件被ViewGroup的子元素成功处理时,mFirstTouchTarget会被赋值并指向子元素
//换句话说,当ViewGroup不拦截,并将事件交给子元素处理时,mFirstTouchTarget != null成立
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;
}

首先判断为ACTION_DOWN事件,会清除掉一些变量、重置状态。
后续有一个变量:final boolean intercepted。如果没有拦截,intercepted为false。这里有一个特殊情况:标志位FLAG_DISALLOW_INTERCEPT。这个标志位是在requestDisallowInterceptTouchEvent方法内设置的,一旦它被设置,那么ViewGroup就无法拦截除DOWN事件以外的其他事件(MOVE/UP等事件)。当ViewGroup分发事件时,DOWN事件会重置该标志位,导致子View的“努力徒劳”。当ViewGroup面临DOWM事件时,总会调用自己的onInterceptTouchEvent方法询问是否需要拦截事件。resetTouchState方法会对该标志位进行重置,故子View调用requestDisallowInterceptTouchEvent方法并不能影响ViewGroup对DOWM事件的处理结果,这一点,在使用内部拦截法处理滑动冲突时会得到验证。

然后往下走,
if (newTouchTarget == null && childrenCount != 0)
newTouchTarget 肯定为null,因为上面没几行刚刚赋值为null。childrenCount 表示控件的个数,肯定不为0,所以就会继续往里走下去,会遇到

final ArrayList<View> preorderedList = buildTouchDispatchChildList();

buildTouchDispatchChildList()—>return buildOrderedChildList():对子view进行排序。

ArrayList<View> buildOrderedChildList() {
    final int childrenCount = mChildrenCount;
    if (childrenCount <= 1 || !hasChildWithZ()) return null;

    if (mPreSortedChildren == null) {
   		 //专门用来存放排序后的child的
        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];
        //获取View在Z轴方向的值
        final float currentZ = nextChild.getZ();

        // insert ahead of any Views with greater Z
        int insertIndex = i;
        //mPreSortedChildren 就是上面刚刚new出来的数组。专门用来存放排序后的children
        //从数组取出来和当前的currentZ比较,while里就是将Z值较大的往后放,较小的往前排。
        while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
            insertIndex--;
        }
        mPreSortedChildren.add(insertIndex, nextChild);
    }
    return mPreSortedChildren;
}

for循环中通过nextChild.getZ()来获取到currentZ,currentZ就是View在Z轴方向上的值(码积木一样,View都是一层层码上去的)。
越靠上的View,它的currentZ就越大。mPreSortedChildren 用来存放排序后的children。while里将Z值大的View放后,Z值小的往前排。
最后将排序好的数组返回。

preorderedList 就是return的排好序的数组,随后还会经历一个for循环。对排好序的数组的循环从childrenCount -1开始,也就是先从数组最后开始。通过getAndVerifyPreorderedIndex方法获取对应的childIndex,并进一步获取到响应的child(就是我们的Button等控件View)。

						//从数组最后面,currentZ最大的开始
                        for (int i = childrenCount - 1; i >= 0; i--) {
							//先拿到View的下标
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
							//根据下标,得到View(如:Button)		
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

							//拿到Button后,canViewReceivePointerEvents判断点击是否是动画、是否可见,
							//isTransformedTouchPointInView判断点击是否在View上。
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                // Child is already receiving touch within its bounds.
                                // Give it the new pointer in addition to the ones it is handling.
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
							//dispatchTransformedTouchEvent负责将事件分发给谁来处理事件,return handled = child.dispatchTouchEvent(transformedEvent);
							//这意味这事件已经传递到View中了。
							//View#dispatchTouchEvent的返回结果--->result  只有当onTouchEvent处理后,result才为true。
							//换句话说,当Button处理了事件,才会命中这个if,break掉for循环;如果Button没处理,就开始下一次循环,取出比Button的currentZ值更小的父容器,将事件交给它。
							//Button处理事件就处理,Button不处理,就往上传递交给Button的父容器。如果父容器也不处理,那就交给父容器的父容器......
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // 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;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
								//Button处理事件后,调用addTouchTarget方法--->mFirstTouchTarget!=null--->newTouchTarget==mFirstTouchTarget
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }

将事件从ViewGroup分发给View,这样事件的处理就走到了View上,于是事件就得到了它应有的处理(这个方法不止一次被调用,而且有很大区别,第三个参数是否为null):

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    ......
    //child不为null,就执行else中的child.dispatchTouchEvent(transformedEvent)方法
    //也就是调用Button的dispatchTouchEvent方法。
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        transformedEvent.offsetLocation(offsetX, offsetY);
        if (! child.hasIdentityMatrix()) {
            transformedEvent.transform(child.getInverseMatrix());
        }
        //这调用的就是Button的dispatchTouchEvent方法,即事件已经传递到View中了
        handled = child.dispatchTouchEvent(transformedEvent);
    }

    // Done.
    transformedEvent.recycle();
    return handled;
}
返回的handled,即handled = child.dispatchTouchEvent(transformedEvent)

也就是返回View#dispatchTouchEvent的返回结果--->result
public boolean dispatchTouchEvent(MotionEvent event) {
    // If the event should be handled by accessibility focus first.
    if (event.isTargetAccessibilityFocus()) {
        // We don't have focus or no virtual descendant has it, do not handle the event.
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        // We have focus and got the event, then use normal event dispatch.
        event.setTargetAccessibilityFocus(false);
    }

    boolean result = false;

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        stopNestedScroll();
    }

    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        //当onTouchEvent处理后,result才为true。
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }

    // Clean up after nested scrolls if this is the end of a gesture;
    // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    // of the gesture.
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}

再回到ViewGroup#dispatchTouchEvent中的if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))中。如果事件被Button处理完,会返回handled (= child.dispatchTouchEvent(transformedEvent)),View#dispatchTouchEvent方法的返回值和View自己的onTouchEvent有关。看ViewGroup#dispatchTouchEvent,如果Button没有处理事件,就会开启下一个循环,currentZ会更小,事件就会流转到父容器的手里。如果父容器也不处理,那就继续向上return。
可是如果在for循环中,Button处理掉了事件,就会break,即终止了for循环,也就是终止了事件的分发,别人就处理不了了。所以如果Button一旦处理了,Button的父容器就拿不到这个事件了。
当Button处理完事件后,调用addTouchTarget方法:

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    mFirstTouchTarget = target;
    return target;
}

意味着newTouchTarget==mFirstTouchTarget、alreadyDispatchedToNewTouchTarget = true

再往后,mFirstTouchTarget != null,走else:

// Button处理事件后,mFirstTouchTarget != null,进入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循环内的操作,意味着while只会进去一次。
                while (target != null) {
					//next为null
                    final TouchTarget next = target.next;
					//alreadyDispatchedToNewTouchTarget就是true,上面刚刚给赋的值
					//两个条件都满足,即handled=true;while只循环一次,故退出循环后,直接就将handled = true返回了。
                    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;
					//将null给到target
                    target = next;
                }
            }

最后将handled返回就完事了。

滑动冲突

说起滑动冲突,我们都遇到过。ViewPager嵌套ListView,如果ViewPager的onInterceptTouchEvent return了true后,ListView的世界都坍塌了。

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;
}

是否执行onInterceptTouchEvent,取决于disallowIntercept是否为false。假设onInterceptTouchEvent被执行了,即intercepted=true,下面的if 就不会再走了:

//没拦截,就往里进了
            if (!canceled && !intercepted) {

                // If the event is targeting accessibility focus we give it to the
                // view that has accessibility focus and if it does not handle it
                // we clear the flag and dispatch the event to all children as usual.
                // We are looking up the accessibility focused host to avoid keeping
                // state since these events are very rare.
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;

                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                    // Clean up earlier touch targets for this pointer id in case they
                    // have become out of sync.
                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
					
					//newTouchTarget在上面没几行就置空了。
					//childrenCount 表示控件的个数,肯定不为0
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
						
                        // buildTouchDispatchChildList:对子View根据currentZ进行排序
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
						//从数组最后面,currentZ最大的开始
                        for (int i = childrenCount - 1; i >= 0; i--) {
							//先拿到View的下标
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
							//根据下标,得到View(如:Button)		
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

							//拿到Button后,canViewReceivePointerEvents判断点击是否是动画、是否可见,
							//isTransformedTouchPointInView判断点击是否在View上。
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                // Child is already receiving touch within its bounds.
                                // Give it the new pointer in addition to the ones it is handling.
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
							//dispatchTransformedTouchEvent负责将事件分发给谁来处理事件,return handled = child.dispatchTouchEvent(transformedEvent);
							//这意味这事件已经传递到View中了。
							//View#dispatchTouchEvent的返回结果--->result  只有当onTouchEvent处理后,result才为true。
							//换句话说,当Button处理了事件,才会命中这个if,break掉for循环;如果Button没处理,就开始下一次循环,取出比Button的currentZ值更小的父容器,将事件交给它。
							//Button处理事件就处理,Button不处理,就往上传递交给Button的父容器。如果父容器也不处理,那就交给父容器的父容器......
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // 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;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
								//Button处理事件后,调用addTouchTarget方法--->mFirstTouchTarget!=null--->newTouchTarget==mFirstTouchTarget
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        // Did not find a child to receive the event.
                        // Assign the pointer to the least recently added target.
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

这也就意味着,这个ViewGroup的多层子View们就不会有得到事件的机会了。从ViewGroup这就已经拦截了。

然后就往下走if,这里mFirstTouchTarget肯定为null,因为它在上面那一大段if里面的addTouchTarget方法内才被赋值。

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循环内的操作,意味着while只会进去一次。
                while (target != null) {
					//next为null
                    final TouchTarget next = target.next;
					//alreadyDispatchedToNewTouchTarget就是true,上面刚刚给赋的值
					//两个条件都满足,即handled=true;while只循环一次,故退出循环后,直接就将handled = true返回了。
                    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;
					//将null给到target
                    target = next;
                }
            }

还是会调用dispatchTransformedTouchEvent方法,但是参数有改变,child为null。这样就会走到dispatchTransformedTouchEvent方法的if中:

if (child == null) {
    handled = super.dispatchTouchEvent(transformedEvent);
} else {
......

事件分发机制并不是洪水猛兽_第2张图片
调用的是super的dispatchTouchEvent方法,并非else中的child.dispatchTouchEvent方法。即调用的是ViewGroup自己的dispatchTouchEvent方法。即这个事件由ViewGroup自己处理。这也是当重写的onInterceptTouchEvent方法返回true后,其包含的子View就不会滑动了的原因所在。

处理滑动冲突

1. 内部拦截法

即父容器不进行拦截,让子View进行拦截。如果子View需要该事件,就消耗掉。否则,就还给父容器处理。和分发机制有点区别,内部拦截法需要requestDisallowInterceptTouchEvent方法的配合才行(上面已经结合源码解释过了),还得重写子View的分发方法dispatchTouchEvent方法:

    //内部拦截法
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                int deltaX = x - mLastX;
                int deltaY = y - mLastY;
                //父容器需要该事件
                if (Math.abs(deltaX) > Math.abs(deltaY)) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                break;

            }
            default:
                break;
        }

        mLastX = x;
        mLastY = y;
        return super.dispatchTouchEvent(event);
    }

除此之外,父容器也得默认拦截除DOWN外的其他事件,只有这样,当子View调用getParent().requestDisallowInterceptTouchEvent(false)时,父容器才会继续拦截所需的事件。上面提到过,DOWN事件受标志位FLAG_DISALLOW_INTERCEPT控制。一旦父容器拦截DOWN事件,所有的事件就都无法传递到子元素中去了,内部拦截法也就成了“黄粱一梦”。

2.外部拦截法

即父容器进行拦截,如果父容器需要该事件就拦截,反之不拦截。外部拦截相比于内部拦截,更简单。外部拦截的做法,更符合事件分发机制。外部拦截法比较“省心”,只需要重写父容器的onInterceptTouchEvent方法即可:

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        // 外部拦截法
        int x = (int) event.getX();
        int y = (int) event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                mLastX = (int) event.getX();
                mLastY = (int) event.getY();
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                int deltaX = x - mLastX;
                int deltaY = y - mLastY;
                if (Math.abs(deltaX) > Math.abs(deltaY)) {
                    return true;
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                break;
            }
            default:
                break;
        }

        return super.onInterceptTouchEvent(event);
    }

你可能感兴趣的:(Android重点难点)