Android:事件分发机制源码解读与滑动冲突解决方案

事件分发源代码分析

1. Activity 事件分发

首先从 Activity 的 dispatchTouchEvent 方法入手

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

可以看出,Activity 其实是调用了 Window 的 superDispatchTouchEvent 方法,而 Window 的实现类是 PhoneWindow,因此我们直接查看 PhoneWindow 的 superDispatchTouchEvent 方法

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

发现是直接调用的 DecorView 的 superDispatchTouchEvent 方法,再进一步查看

public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

原来这儿就调用了 ViewGroup 的 dispatchTouchEvent 方法,也就是说界面上的事件直接传递给了根布局的 dispatchTouchEvent 方法

2. ViewGroup 事件分发

在讲 ViewGroup 的 dispatchTouchEvent 方法之前,我们先看看 ViewGroup 的 dispatchTransformedTouchEvent 方法,dispatchTouchEvent 内部多次调用了 dispatchTransformedTouchEvent 方法。因为代码量较多,这里只提取我们关心的部分

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 不为空,则调用 child 的 dispatchTouchEvent 方法,否则调用自身的 dispatchTouchEvent 方法
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }

    ......

    // 如果传入的 child 不为空,则调用 child 的 dispatchTouchEvent 方法,否则调用自身的 dispatchTouchEvent 方法
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        ......
        handled = child.dispatchTouchEvent(transformedEvent);
    }

    ......
    return handled;
}

可以看出 dispatchTransformedTouchEvent 方法主要做了两件事

  • 如果传入的事件是 ACTION_CANCEL,或者 cancel 参数为 true,则直接分发 ACTION_CANCEL 事件
  • 分发过程中,如果 child 为空,则调用当前 View 的 super.dispatchTouchEvent 方法,这是因为 ViewGroup 的 dispatchTouchEvent 方法会被重写,而此时调用 super 的方法也就是调用 View 的 dispatchTouchEvent 方法;如果 child 不为空,则调用这个子 View 的 dispatchTouchEvent 方法。

然后我们再来看看 dispatchTouchEvent 方法,同样代码量特别多,我们只抽取我们关心的,即使这样代码量依然很多。我们先列出来,不用仔细看,后面会分块拆分讲解

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ......
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // 1. DOWN 事件进行初始化,清空 TouchTargets 和 TouchState
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        // 2. 检查是否拦截
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            // 是否强制不允许拦截,子 View 可以设置 parent 强制不允许拦截,默认为 false
            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 {
            intercepted = true;
        }

        ......
        // 3. 如果没有被拦截, 先处理 DOWN 事件,主要是赋值 TouchTarget
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        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) {
                    ......
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);
                        ......
                        // 找到 Visible 并且处于点击范围的子 View
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }
                        ......
                        // 相当于调用子 View 的 dispatchTouchEvent 方法
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            ......
                            // 赋值 TouchTarget,刷新标志位
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                        ......
                    }
                    ......
                }
                ......
            }
        }

        // 4. 是自己处理事件还是交由子 View 处理事件
        if (mFirstTouchTarget == null) {
            // 没有子 View 消耗事件,则自己消耗,相当于调用 super.dispatchTouchEvent 方法
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {
                final TouchTarget next = target.next;
                // 如果是 DOWN 事件,则上面已经调用了子 View 的 dispatchTouchEvent 方法,则什么都不用做
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    // 根据 intercepted 决定是否将事件强制改为 CANCEL 事件
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    // 相当于调用子 View 的 dispatchTouchEvent 方法。如果 intercepted=true,此时会强制将 action 改为 CANCEL;如果 intercepted=false,则
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    // 如果 intercepted=true,则将 mFirstTouchTarget 置为 null
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }
        ......
    }
    ......
    return handled;
}

dispatchTouchEvent 方法主要由4个模块组成的

  1. DOWN 事件进行初始化,清空 TouchTargets 和 TouchState
  2. 检查是否拦截
  3. 如果没有被拦截, 先处理 DOWN 事件,主要是赋值 TouchTarget
  4. 是自己处理事件还是交由子 View 处理事件

A. 第一步:DOWN 事件时进行初始化

// 1. DOWN 事件进行初始化,清空 TouchTargets 和 TouchState
if (actionMasked == MotionEvent.ACTION_DOWN) {
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}

我们知道,一个事件序列是由一个 ACTION_DOWN,零个或多个 ACTION_MOVE 和一个 ACTION_UP 组成的。ACTION_DOWN 是一个事件序列的开始,ACTION_UP 是一个事件序列的结束。在 ACTION_DOWN 时,需要对一些状态进行初始化和重置。上面的 cancelAndClearTouchTargets 和 resetTouchState 方法,都是初始化一些状态,最重要的我们关心的就是内部调用了这个初始化代码

private void clearTouchTargets() {
    TouchTarget target = mFirstTouchTarget;
    if (target != null) {
        do {
            TouchTarget next = target.next;
            target.recycle();
            target = next;
        } while (target != null);
        mFirstTouchTarget = null;
    }
}

可以看出来这里是用循环的方式把单链表 mFirstTouchTarget 给清空了,至于 mFirstTouchTarget 是什么这里先不讲,只需要知道它保存了一个指定消耗事件的子 View 便可,后续的所有事件会直接交给这个 View 消耗

B. 第二步:检查是否拦截

// 2. 检查是否拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
    // 是否强制不允许拦截,子 View 可以设置 parent 强制不允许拦截,默认为 false
    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 {
    intercepted = true;
}

这里我们可以看出,如果是 ACTION_DOWN 事件,或者 mFirstTouchTarget 不为空的时候,就会进入是否拦截的逻辑判断。这里有一个 disallowIntercept 的判断,这个标记是子 View 设置 ViewGroup 是否允许拦截的情况,这里我们暂不用理解这种情况,默认 disallowIntercept 为 false,也就是允许拦截。此时会判断 onInterceptTouchEvent 方法,而 onInterceptTouchEvent 方法默认返回 false。

而 mFirstTouchTarget 是怎么来的呢?在后面会有详细的讲解,这里先简单的说明一下:

  • 如果 intercepted 为 false,并且在后面有一个子 View 的 dispatchTouchEvent 方法在 ACTION_DOWN 时返回了 true,那么就会对 mFirstTouchTarget 进行赋值;
  • 如果 intercepted 为 true,那么在后面就会对 mFirstTouchTarget 置为 null

我们来走一遍所有可能的路径:

  1. 事件序列开始,也就是说,在 ACTION_DOWN 时,会调用 onInterceptTouchEvent 判断是否拦截;
  2. 如果此时 onInterceptTouchEvent 返回 true,intercepted 赋值为 true,则后面 mFirstTouchTarget 不会再被赋值,mFirstTouchTarget 会一直为 null,后续的 ACTION_MOVE 和 ACTION_UP 事件 onInterceptTouchEvent 方法不会再被调用,intercepted 会一直为 true
  3. 如果此时 onInterceptTouchEvent 返回 false,intercepted 赋值为 false,后面如果有子 View 的 dispatchTouchEvent 方法返回了 true,mFirstTouchTarget 会被赋值,后续的 ACTION_MOVE 和 ACTION_UP 事件每次都会调用 onInterceptTouchEvent 方法
  4. 在后续的 ACTION_MOVE 和 ACTION_UP 事件中,如果 onInterceptTouchEvent 返回了 true,那么在后面会清空 mFirstTouchTarget 的值,那么在下一个事件直到最后一个事件,onInterceptTouchEvent 方法又不会被调用了

总结来说,onInterceptTouchEvent 一旦某一次返回了 true,那么后面的事件都不会再调用 onInterceptTouchEvent 进行是否拦截的判断,intercepted 的值会一直为 true

C. 第三步:先处理 DOWN 事件

// 3. 如果没有被拦截, 先处理 DOWN 事件,主要是赋值 TouchTarget
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
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) {
            ......
            for (int i = childrenCount - 1; i >= 0; i--) {
                final int childIndex = getAndVerifyPreorderedIndex(
                        childrenCount, i, customOrder);
                final View child = getAndVerifyPreorderedView(
                        preorderedList, children, childIndex);
                ......
                // 找到 Visible 并且处于点击范围的子 View
                if (!canViewReceivePointerEvents(child)
                        || !isTransformedTouchPointInView(x, y, child, null)) {
                    ev.setTargetAccessibilityFocus(false);
                    continue;
                }
                ......
                // 相当于调用子 View 的 dispatchTouchEvent 方法
                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                    ......
                    // 赋值 TouchTarget,刷新标志位
                    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                    alreadyDispatchedToNewTouchTarget = true;
                    break;
                }
                ......
            }
            ......
        }
        ......
    }
}

这段代码的前提条件就是 intercepted 为 false,并且是 ACTION_DOWN 事件,此时会遍历每一个满足条件(处于Visible状态并且处于点击范围内)的子 View,调用了 dispatchTransformedTouchEvent 方法,dispatchTransformedTouchEvent 方法最开始我们就讲解过,此时的 child 参数不为空,所以就是调用的 child 的 dispatchTouchEvent 方法。如果 dispatchTransformedTouchEvent 返回为 true,则调用 addTouchTarget 方法对 mFirstTouchTarget 进行赋值,并且将变量 alreadyDispatchedToNewTouchTarget 置为 true,然后紧跟了一个 break 跳出循环,为什么要跳出循环呢?这是因为一个事件只能被一个 View 消耗(dispatchTouchEvent 返回 true 代表消耗)。

我们看看 addTouchTarget 方法

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

就是一个单链表操作,新增一个 TouchTarget 并插入到表头,然后将表头赋值给 mFirstTouchTarget。

总结来说,ACTION_DOWN 时,如果 intercepted 为 true,则不会有任何子 View 调用 dispatchTouchEvent 方法,并且 mFirstTouchTarget 不会被赋值,会一直为 null;如果 intercepted 为 false,那么在满足条件的某个子 View 的 dispatchTouchEvent 方法返回 true 时,mFirstTouchTarget 也会被赋值

D. 第四步:分发事件

// 4. 是自己处理事件还是交由子 View 处理事件
if (mFirstTouchTarget == null) {
    // 没有子 View 消耗事件,则自己消耗,相当于调用 super.dispatchTouchEvent 方法
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
} else {
    TouchTarget predecessor = null;
    TouchTarget target = mFirstTouchTarget;
    while (target != null) {
        final TouchTarget next = target.next;
        // 如果是 DOWN 事件,则上面已经调用了子 View 的 dispatchTouchEvent 方法,则什么都不用做
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
            handled = true;
        } else {
            // 根据 intercepted 决定是否将事件强制改为 CANCEL 事件
            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                    || intercepted;
            // 相当于调用子 View 的 dispatchTouchEvent 方法。如果 intercepted=true,此时会强制将 action 改为 CANCEL;如果 intercepted=false,则
            if (dispatchTransformedTouchEvent(ev, cancelChild,
                    target.child, target.pointerIdBits)) {
                handled = true;
            }
            // 如果 intercepted=true,则将 mFirstTouchTarget 置为 null
            if (cancelChild) {
                if (predecessor == null) {
                    mFirstTouchTarget = next;
                } else {
                    predecessor.next = next;
                }
                target.recycle();
                target = next;
                continue;
            }
        }
        predecessor = target;
        target = next;
    }
}

可以看到,只要 mFirstTouchTarget 为 null,那么就会直接调用 dispatchTransformedTouchEvent 方法,child 参数为 null,那么就会调用当前 ViewGroup 的 super.dispatchTouchEvent 方法。

如果 mFirstTouchTarget 不为 null,首先判断 alreadyDispatchedToNewTouchTarget 和 newTouchTarget 变量决定是否直接返回。由上面第3步可知,如果 alreadyDispatchedToNewTouchTarget 为 true,则代表当前是 ACTION_DOWN 事件,并且找到了一个子 View 并且调用过了 child 的 dispatchTouchEvent 方法,因此这里什么都不用做。

然后再看看 cancelChild 变量,它是由 intercepted 决定的。假如 cancelChild 为 false,说明 intercepted 一定为 false,然后因为 mFirstTouchTarget 不为空,那么调用 dispatchTransformedTouchEvent 时 child 不为空,则直接调用 child 的 dispatchTouchEvent 方法;如果 intercepted 为 true,那么 cancelChild 一定为 true,dispatchTransformedTouchEvent 的 cancel 参数为 true,那么会直接给 mFirstTouchTarget 对应的 child 传递一个 ACTION_CANCEL 事件,然后在 mFirstTouchTarget 对应的单链表中,删除 mFirstTouchTarget 当前对应的 TouchTarget

总结一下,如果 mFirstTouchTarget 为 null,则调用自身的 super.dispatchTouchEvent 方法;如果 mFirstTouchTarget 不为 null,如果上面已经处理过 ACTION_DOWN 事件,则什么都不做;如果 intercepted 为 false,则直接调用 mFirstTouchTarget 对应的 child 的 dispatchTouchEvent方法;如果 intercepted 为 true,则会强制给 mFirstTouchTarget 对应的 child 分发一个 ACTION_CANCEL 事件,然后将 mFirstTouchTarget 置为 null

E. ViewGroup 的 dispatchTouchEvent 事件分发流程

我们将一个完整的事件序列的整体流程走一遍

  1. 当 ACTION_DOWN 事件时,会将 mFirstTouchTarget 置为 null
  2. 然后判断 onInterceptTouchEvent 是否拦截,如果返回 true,则后续事件都不会再调用 onInterceptTouchEvent 方法来判断是否拦截并且默认都是 true
  3. 因为 intercepted 为 true,并且 mFirstTouchTarget 为 null,因此会直接调用自身的 super.dispatchTouchEvent 方法。并且后续事件都会如此执行(若下一个事件能到达)。
  4. 如果第2步的 onInterceptTouchEvent 返回 false,则会尝试找到一个满足条件的子 View,分发此次 ACTION_DOWN 事件,并对 mFirstTouchTarget 进行赋值
  5. 如果第4步找不到合适的子 View,mFirstTouchTarget 依然为 null,则会调用自身的 super.dispatchTouchEvent 方法。并且后续事件都会如此执行(若下一个事件能到达)。
  6. 如果第4步找到了合适的子 View,也就意味着分发了一个 ACTION_DOWN 事件,那么 ACTION_DOWN 就什么都不用做了。
  7. 当 ACTION_MOVE 和 ACTION_UP 事件时,如果第4步找到了合适的子 View,mFirstTouchTarget 被赋值,那么会再次判断 onInterceptTouchEvent 是否拦截。
  8. 如果第7步判断为不拦截,则直接分发此次事件给 mFirstTouchTarget 对应的 child
  9. 如果第7步判断为拦截,则强制分发 ACTION_CANCEL 事件给 mFirstTouchTarget 对应的 child,然后将 mFirstTouchTarget 置为 null

3. View事件分发

public boolean dispatchTouchEvent(MotionEvent event) {
    ......
    if (onFilterTouchEventForSecurity(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;
}

看完了 ViewGroup 的 dispatchTouchEvent,再来看 View 的 dispatchTouchEvent 就发现是不是太简单了。这里简单提醒一下:在 ViewGroup 调用 super.dispatchTouchEvent 其实就是调用的 View 的 dispatchTouchEvent 方法。

首先,如果设置了 mOnTouchListener,则会优先处理 mOnTouchListener 的 onTouch 方法,如果 onTouch 方法返回 true,则此方法直接返回而不会再调用 onTouchEvent 方法了。也就是说 mOnTouchListener 的优先级高于 onTouchEvent 方法。如果在 mOnTouchListener 的 onTouch 方法中返回了 true,也许会因为没有执行 onTouchEvent 方法而导致点击等回调不被调用。最后会将 mOnTouchListener 或 onTouchEvent 方法的返回值作为此方法的返回值。

然后我们看看 onTouchEvent 方法

public boolean onTouchEvent(MotionEvent event) {
    ......
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        ......
        return clickable;
    }
    ......

    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                ......
                boolean focusTaken = false;
                if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                    focusTaken = requestFocus();
                }
                ......
                if (!focusTaken) {
                    if (mPerformClick == null) {
                        mPerformClick = new PerformClick();
                    }
                    if (!post(mPerformClick)) {
                        performClickInternal();
                    }
                }
                ......
                break;
            ......
        }
        return true;
    }
    return false;
}

首先,只要可以点击或者长按,clickable 就为 true。不管当前控件是不是 DISABLED,都返回 clickable。clickable 根据不同的控件而值不同,比如 Button 就为 true,TextView 就为 false。设置对应的点击监听器会默认将 clickable 设为 true。也就是说,只要当前控件是可点击的,那么 onTouchEvent 默认返回 true 而消耗这一串事件序列。

在 ACTION_UP 中,会完成点击事件的判断,具体是通过 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() {
    // 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;
}

在这里,显示触发 mOnClickListener 的 onClick 方法来完成点击事件的回调。

滑动冲突解决

根据以上源代码分析,我们可以找到一种解决滑动冲突的套路:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    boolean intercept = false;
    float x = ev.getX();
    float y = ev.getY();
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            intercept = false;
            break;
        case MotionEvent.ACTION_MOVE:
            // 横向滑动则自己处理,竖向滑动则子View处理
            if (Math.abs(x - lastX) > Math.abs(y - lastY)){
                intercept = true;
            } else {
                intercept = false;
            }
            break;
        case MotionEvent.ACTION_UP:
            intercept = false;
            break;
    }
    lastX = x;
    lastY = y;
    return intercept;
}
  • ACTION_DOWN 事件一定要返回 false,否则所有的事件都会被 ViewGroup 拦截,并且不会再调用 onInterceptTouchEvent 方法。因此 ACTION_DOWN 事件一定会被子 View 所消耗。如果当前 ViewGroup 需要在 ACTION_DOWN 处理一些逻辑,则可以在 dispatchTouchEvent 或者 onInterceptTouchEvent 方法处理
  • ACTION_MOVE 事件根据需要来决定是否拦截,一旦拦截,则会立马给 mFirstTouchTarget 指定的 child 分发一个 ACTION_CANCEL 事件,后续的事件序列都直接交由 ViewGroup 消耗,并且不会再调用 onInterceptTouchEvent 方法
  • ACTION_UP 事件的返回值返回 false,如果在之前的事件序列返回过 true,那么 ACTION_UP 返回什么都无所谓,因为根本不会被执行到;但是如果之前的事件序列没有返回过 true,那么 ACTION_UP 返回 true,会直接强制替换为 ACTION_CANCEL 分发给子 View,导致子 View 的点击事件无法响应。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容