Android View 事件分发机制

【Android】View的事件分发机制 `dispatchTouchEvent(MotionEvent event)` 方法是事件分发的入口。 - 每当事件产生时(如点击、滑动),系统会将该事件封装成一个 `MotionEvent` 对象,并通过 `dispatchTouchEvent` 方法传递给根视图(通常是 `Activity` 中的 `DecorView`)。 - 在 `dispatchTouchEvent` 中,事件会根据层级逐层传递给子视图,直到找到可以处理事件的视图为止。 - 若 `dispatchTouchEv 阅读详情

感谢 Android 开发艺术探索

点击事件的传递

Android 事件分发的三个方法: dispatchTouchEvent, onInterceptTouchEvent, onTouchEvent

dispatchTouchEvent
用来进行事件的分发, 如果事件能够传递到当前 view , 那么该方法一定会被调用, 返回结果受当前 View 的 onTouchEvent 和下级 view 的 dispatchTouchEvent 影响 , 表示是否消耗当前事件。

onInterceptTouchEvent
在 dispatchTouchEvent 方法内部调用, 用来判断是否拦截某个事件,如果当前 View 拦截了某个事件,那么在同一个事件序列当中, 此方法不会被调用, 返回结果表示是否拦截该事件。

onTouchEvent
也是在 dispatchTouchEvent 方法中调用,用来处理点击事件, 如果不消耗那么在当前 view 无法再接收到事件

点击事件传递规则:
对于一个根 ViewGroup 来说, 当一个点击事件产生后, 首先会传递给它的 dispatchTouchEvent 来分发, 接着会调用它的 onInterceptTouchEvent 判断是否拦截这个事件, 如果拦截该事件, 那么当前事件将又这个 ViewGroup 自己处理, 如果 onInterceptTouchEvent 返回 false, 那么将交由它的子 View 的 dispatchTouchEvent 方法去处理,注意, View 中并不包含 onInterceptTouchEvent 方法, 如果传递到了 view, 那么它的 onTouchEvent 方法将会被调用 ,如此反复。

当一个 View 需要处理事件时, 如果设置了 onTouchListener, 那么 onTouchListener 中的 onTouch 方法会被调用, 此时如果 onTouch 方法返回 false, 那么它的 onTouchEvent 将会被调用, 如果返回 true , onTouchEvent 方法将不会被调用,如果 view 设置了 onClickListener 那么它的 onClick 事件将会在 onTouchEvent 的 MotionEvent.ACTION_UP 里调用。 如果 onTouchEvent 里返回了 false, 那么它的父类的 onTouchEvent 将会被调用, 因为它自己处理不了, Android开发艺术探索中描述了一个例子:假如有一个难题领导交给了一个程序员去处理, 结果这个程序员搞不定(onTouchEvent 返回false), 那么这个难题将返回给更高级的上级去处理。

关于事件机制有这样一些结论(后面分析源码会更清晰)

  1. 同一个事件从手指接触屏幕的那一刻起, 到手指抬起,事件从 down 事件开始, 经历多个 move 事件, 最终 up 事件结束
  2. 正常情况下, 一个事件序列只能被一个 view 消耗(要么是子 view, 要么是父 view)
  3. 某一个 view 一旦决定拦截事件, 那么这个事件序列都将由它来处理
  4. 某个 view 一旦开始处理事件,如果它不消耗 ACTION_DOWN 事件,那么同一事件序列中的其他事件都不会交给它来处理(后续分下源码理解这个), 并且事件将重新交由它的父元素去处理, 即父元素的 onTouchEvent 会被调用, 意思就是事件一旦交给一个 view 去处理, 那么它就必须消耗掉, 否则同一个事件序列中剩下的事件就不再交给它来处理了
  5. 如果 view 不消耗 除 ACTION_DOWN 以外的其他事件, 那么这个点击事件会消失, 此时父元素的 onTouchEvent 并不会被调用, 并且当前 view 可以持续的收到后续的事件, 最终这些消失的点击事件将会传递给 Activity 处理
  6. ViewGroup 默认不拦截任何事件
  7. View 中没有 onInterceptTouchEvent 方法, 一旦有点击事件传递给它, 那么它的 onTouchEvent 会被调用
  8. View 的 onTouchEvent 会默认消耗掉事件,除非它是不可点击的(clickable 和 : 都是false),View 的 longClickable 默认都为 false, clickable 要分情况, 必须 Button 的 clickable 属性默认为 true, TextView 的 clickable 属性默认为 false。
  9. View 的 enable 属性不影响 onTouchEvent 的默认返回值, 哪怕一个 View 是 disable 状态的, 只要它的 clickable 或者 longClickable 有一个是true, 那么它的 onTouchEvent 就会返回true。
  10. Onclick 会发生的前提是 View 是可点击的, 并且收到了 down 和 up 事件
  11. 事件传递过程是由外向内的, 即事件总是先传递到父元素,然后由父元素分发给子 view, 通过 requestDisallowInterceptTouchEvent 方法可以在子元素中干预父元素的事件分发过程, 但是 ACTION_DOWN 事件除外。

    源码解析

    点击事件用 MotionEvent 表示, 事件最开始由 Activity 开始传递, 看下面代码, 刚开始有 Activity 的 diapatchTouchEvent 开始分发,由 Activity 传递给 Window, Window 传递给 DecorView , DecorView 其实是 setContentView 的 view 的父容器。

/**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

看上面的代码, 首先会交给 Window 来分发, 如果返回 true 那么整个循环就结束了, 如果返回了 false, 那么意味着子 view 都没有处理这个事件, 那么 Activity 会调用自己的 onTouchEvent 自己进行处理。

接下来 Window 会将事件分发到 ViewGroup:

/**
     * Used by custom windows, such as Dialog, to pass the touch screen event
     * further down the view hierarchy. Application developers should
     * not need to implement or call this.
     *
     */
    public abstract boolean superDispatchTouchEvent(MotionEvent event);

通过查看 window 的 superDispatchTouchEvent 方法发现它是个抽象方法, 那么必定有它的实现类, 从 window 的源码可知,它的实现类是 PhoneWindow

/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */

那么查看一下 PhoneWindow 是怎么进行分发的,

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

很明显这里传递给了 DecorView 去继续分发,查看 DecorView 源码可知

public class DecorView extends FrameLayout 

DecorView 继承了 FrameLayout,是一个 ViewGroup , 这就清楚了, 那么 DecorView 的子 view 不就是 setContentView 里面的 view 么, 这个 view 一般也都是一个 ViewGroup, 这样才能在上面添加各种布局啊。

ViewGroup 怎么进行事件传递上面已经说了, 那么看一下它的 diapatchTouchEvnet 方法的拦截情况,

             // Check for interception.
            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;
            }

从源码中可以发现, ViewGroup 会在2种情况下会判断是否拦截当前事件, 事件类型为 actionMasked ==MotionEvent.ACTION_DOWN || mFirstTouchTarget != null, 那么这个 mFirstTouchTarget 是什么, 后面代码会详细分析, 当事件由 ViewGroup 的子元素成功处理时, mFirstTouchTarget 会被赋值并指向子元素, 换句话说, 当 ViewGroup 不拦截事件把事件交给子元素处理时 mFirstTouchTarget != null , 反过来, 一旦事件交由 ViewGroup 拦截, mFirstTouchTarget != null 就不成立, 那么当 ViewGroup 的 move 和 up 事件到来的时候, actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null 判断为 false , 那么这个事件序列中的其他事件也将交给它处理。

当然从源码中发现还有一个标志位可以影响 ViewGroup 的拦截, 那就是 FLAG_DISALLOW_INTERCEPT 这个标志位,

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

从源码中可以看出, requestDisallowInterceptTouchEvent 方法可以改变 mGroupFlags 这个标记位,

        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

如果子 view 通过这个方法改变了标记位 , 那么将影响 ViewGroup 的拦截, 当然拦截的事件是除了 ACTION_DOWN 事件以外的, 因为如果是 ACTION_DOWN,

            // 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(); 重置标记位, 因此子 view 调用改变标记位这个方法并不能影响 ViewGroup ACTION_DOWN 事件的执行, 从上面的分析可以得出结论, 当 ViewGroup 决定拦截事件后, 那么后续的点击事件会默认交给它处理并且不再调用它的 onInterceptTouchEvent 方法, 证实了上面的 3 结论,这个标记位的作用是让 ViewGroup 不拦截事件, 前提是 ViewGroup 不拦截 ACTION_DOWN, 证实了 11 结论 ,这段分析可以总结以下几点:

  1. onInterceptTouchEvent 不会每次都会被调用, 如果要想处理所有点击事件, 那么要选择 dispatchTouchEvent 方法
  2. 既然这个标记位可以影响 ViewGroup 是否拦截事件, 那么我们是否可以用这个标记位来处理滑动冲突

    如果 ViewGroup 不拦截事件, 那么事件将继续向下传递给它的子 View,主要源码如下,

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

                            // If there is a view that has accessibility focus we want it
                            // to get the event first and if not handled we will perform a
                            // normal dispatch. We may do a double iteration but this is
                            // safer given the timeframe.
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

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

首先遍历所有子 View, 然后判断子 view 是否能够接收点击事件, 主要看:

            if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }


    /**
     * Returns true if a child view can receive pointer events.
     * @hide
     */
    private static boolean canViewReceivePointerEvents(@NonNull View child) {
        return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                || child.getAnimation() != null;
    }
 /**
     * Returns true if a child view contains the specified point when transformed
     * into its coordinate space.
     * Child must not be null.
     * @hide
     */
    protected boolean isTransformedTouchPointInView(float x, float y, View child,
            PointF outLocalPoint) {
        final float[] point = getTempPoint();
        point[0] = x;
        point[1] = y;
        transformPointToViewLocal(point, child);
        final boolean isInView = child.pointInView(point[0], point[1]);
        if (isInView && outLocalPoint != null) {
            outLocalPoint.set(point[0], point[1]);
        }
        return isInView;
    }

分别判断了子元素是否在执行动画和点击事件的坐标是否落在了子元素的区域内, 如果满足了这2个条件之一, 那么事件会传递给它处理, 可以发现 dispatchTransformedTouchEvent 方法实际调用了子元素的 dispatchTouchEvent 方法, 这样完成了一次事件分发,

/**
     * Transforms a motion event into the coordinate space of a particular child view,
     * filters out irrelevant pointer ids, and overrides its action if necessary.
     * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
     */
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

}

如果子元素的 dispatchTouchEvent 返回 true, 那么

newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;

newTouchTarget 被赋值, 终止对子元素的遍历,如果返回 false , 那么继续遍历其他子元素, 实际上看下面代码 mFirstTouchTarget 是在 addTouchTarget 被赋值的,

/**
     * Adds a touch target for specified child to the beginning of the list.
     * Assumes the target child is not already present.
     */
    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

mFirstTouchTarget 是一种单链表结构, 它是否被赋值将直接影响 ViewGroup 的拦截策略, 如果 mFirstTouchTarget 是 null, intercepted 将是 true, 那么 ViewGroup 将会拦截事件序列中的所有点击事件, 如果遍历所有子元素后事件都没有被合适的处理, 那么会有2种情况, 一个是 ViewGroup 没有子元素, 还有一种情况是子元素虽然处理了事件, 但是还是在 dispatchTouchEvent 返回了 false , 在这俩种情况下 ViewGroup 都会自己处理点击事件, 证实了 4 结论, 代码如下:

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

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
        }

此时 child 传入了 null, 那么 super.dispatchTouchEvent(event); 它将事件交由 View 类来处理。

View 对点击事件的处理
看 View 中的 dispatchTouchEvent 方法, 注意 这里的 View 不包含 ViewGroup 了, View 是一个单独的元素, 它没有子元素所以事件无法向下传递, 所以只能自己处理

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

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

首先会判断是否有 onTouchListener 如果有那么返回true, 那么看上面代码 onTouchEvent 就不会被调用, 所以 onTouchListener 优先级高于 onTouchEvent, 接着分析 onTouchEvent:


        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return clickable;
        }

当 view 处于 DISABLED 状态时, 它仍然会消耗掉点击事件, 尽管它看起来不可用,但是并不代表它的 onclick 事件可以执行。

if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

如果 view 设置了代理, 那么还会执行代理的 onTouchEvent。

完整 代码:

public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return clickable;
        }
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    if (!clickable) {
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true, x, y);
                        }

                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_DOWN:
                    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                    }
                    mHasPerformedLongPress = false;

                    if (!clickable) {
                        checkForLongClick(0, x, y);
                        break;
                    }

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0, x, y);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    if (clickable) {
                        setPressed(false);
                    }
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    break;

                case MotionEvent.ACTION_MOVE:
                    if (clickable) {
                        drawableHotspotChanged(x, y);
                    }

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        // Remove any future long press/tap checks
                        removeTapCallback();
                        removeLongPressCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            setPressed(false);
                        }
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    }
                    break;
            }

            return true;
        }

        return false;
    }

从上面的代码可以看出, 只要 CLICKABLE 或者 LONG_CLICKABLE 有一个是true , 那么就会消耗掉事件, 不管它是不是 DISABLED 的状态, 证实了上面得 8 , 9 , 10 结论, 那么当 ACTION_UP 发生的时候会调用 performClick , 如果设置了 onClickListener , 就会执行点击事件, 看 performClick 内部:

public boolean performClick() {
        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;
    }

View 的 LONG_CLICKABLE 默认都为 false, 而 CLICKABLE 要分具体的 view, Button 的 CLICKABLE 是 true, TextView 默认为 false, 可以通过 setClickable 和 setLongClickable 来改变它们的属性,另外通过 setOnClickListener 或者通过 setOnLongClickListener 会将 setClickable 或者 setLongClickable 属性设置为 true。

public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }
Android View体系(五)从源码解析View的事件分发机制 三年前写过事件分发机制的文章但是写的不是很好,所以重新再写一篇,关于事件分发机制的文章已经有很多,但是希望我这篇是最简洁、最易懂的一篇。 阅读详情

相关推荐

双摄像头的实时视频拼接及目标跟踪(一)

本章主要讲图像拼接算法的研究 先膜拜一下知乎大佬全面的叙述:https://www.zhihu.com/question/34535199/answer/135169187 本文图像拼接是基于opencv的图像拼接,流程分为以下几步 ...

Y.W.jian的博客 3万+

Android事件分发机制详解:史上最全面、最易懂

点击事件由哪个对象发出,经过哪些对象,最终达到哪个对象并最终得到处理。这里的对象是指Activity、ViewGroup、View事件分发过程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成经过上述3个问题,相信大家已经对Android的事件分发有了感性的认知,接下来,我将详细介绍Android事件分发机制。

w1142203475的博客 1万+

大学生电赛学习之路

可以看到全国大学生电子设计大赛的认可度是非常高的,属于A类学科竞赛。电子设计大赛的形式也是给出题目,参赛者有四天三夜的时间解决问题,题目大致可以分为:控制类、测量类、高频类以及电力电子类。该竞赛会在单数年的9月份举行。但是对于新生来说电赛确实遥不可及的,可能在很多学校本科生是不能进入实验室的,但是就算进了实验室你就有资格去参加电赛或者说能拿一个不错的奖项吗? 现在正值新生开学季,当然会有一大批学生会进入实验室的学习,电赛的举办时间是每年的暑假,所以说对于大一同学来说有一年的时间去准备,那我们就来说一说参加.

weixin_46098612的博客 8788

Android事件分发机制【最新、最全、最易懂、由浅入深】

Android事件分发流程 = Activity -> ViewGroup -> View即:1个点击事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到ViewActivity对点击事件的分发机制ViewGroup对点击事件的分发机制View对点击事件的分发机制Activity事件分发机制、ViewGroup事件分发机制、View事件分发机制当一个点击事件发生时,从Activity的事件分发开始(),流程总结如下:三者介绍。

丨康有为丨的博客 1万+

ai数字人系统部署

这段代码实现了基本的语音交互闭环,用户通过语音输入问题,数字人识别语音后进行自然语言理解并生成回复,再将回复转换为语音输出。但实际应用中的 AI 数字人系统更为复杂,如形象渲染需专业图形引擎(如 Unity、Unreal Engine),自然语言处理也会涉及更复杂的模型微调及多轮对话管理等。一个完整的 AI 数字人系统涵盖形象生成、自然语言处理、动作控制等多个复杂功能,下面为你提供一个 Python 示例,整合多个库来实现数字人基础功能,包括语音交互、简单的自然语言理解以及通过虚拟形象展示对话。

weixin_ggwwsscc的博客 368

Linux基础——“C语言高级编程” C语言中的细节你真的知道吗?

文章目录GCC简介 GCC简介 gcc 所支持后缀名解释 .c c原始程序 .C / .cc /.cxx C++原始程

weixin_44278698的博客 2510

Android事件分发机制

Android的事件分发机制

qq_41095045的博客 2万+

阻抗匹配详解

在深入理解阻抗匹配概念之前,我们可以先来一起看一个现象,首先使用一个信号发生器输出一个脉宽为 50ns 的单脉冲信号,同时将信号接入示波器,此时在示波器的波形视图上可以看到这个仅有一个脉冲的信号,如下图。如果我们将信号接入示波器的同时将电缆延长数米到外部,并且延长后电缆末端保持悬空不接入任何东西,引出一段未接入任何东西的开路电缆,按照常识应该没有任何影响,然而实测是在原有脉冲旁边多出了一个类似脉冲,如下图。

zhbi98 的技术 Blogs 2556

带你深入了解Android的事件分发机制

事件拦截(Event Intercept):事件拦截是指在事件分发过程中,某个ViewGroup可以拦截事件的传递,阻止它传递给子View。Android的事件分发机制是指在Android系统中,如何将用户的触摸事件、按键事件等传递给正确的View进行处理的一套机制。值得注意的是,在编写代码时,需要仔细理解每个方法的作用和调用时机,确保事件的正确传递和处理,避免潜在的问题和冲突。通过重写以上方法,你可以根据自己的需求来实现事件的分发和处理逻辑,例如拦截特定的手势,定制化事件的响应等。

Dakaring的专栏 2680

ADS2023仿真变容二极管特性

仿真S-Param仿真控件,设置为Single Point,将频率设置为1MHz,同时勾选Z-parameters,取消S-parameters,从元件库中选择元器件搭建原理图,并根据器件spice提供的相关参数,对原理图中器件参数进行修改,修改完成的电路图如下图所示。添加var控件和MeasEqn控件,相关设置如图所示,Ct为变容管的电容值,Q0为其品质因数。因此,对变容二极管的模型仿真结果是可以信赖的。将其解压,在spice文件夹中,可以找到我们想要的器件参数,和仿真简要电路图。

weixin_45811090的博客 1万+

波克 上海 Unity开发笔试题 2024

主要分为四部分,如果没有准备好,不要点击笔试链接,进入就会自动开始。

z2014z的博客 3392

Android(复习篇): 事件分发机制

事件分发概念及其流程: 1.事件分发: 通过手指对屏幕的触摸完成整个交互的过程 用户通过屏幕与手机交互的时候,每一次点击、长按、移动等都是一个事件 事件分发机制就是某一个事件从屏幕传递给各个view,由view来使用这一事件(消费事件)或忽略这一事件(不消费),这个整个过程的控制。 2.那么事件分发的对象是谁? 系统把事件封装为MotionEvent对象,事件分发的过程就是MotionE...

手写我对你的温柔 3193

SAP会计凭证导入ABAP开发模板+注意点—附带源码

开发并不仅仅只要考虑实现的功能,只实现功能的是程序,考虑到使用场景的是工程。使用bapi校验创建会计凭证的数据,报错和参数和正式创建一致。用途,防止产生错误的凭证,即使财务凭证可以冲销。正式创建会计凭证。隐藏的坑。

小九啥也不会 1232

Android View的事件分发机制

Android View的事件分发机制

m0_74448051的博客 1152

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

《Android View 的事件分发机制解析》

前言: 用户按下触摸屏幕的事件。ACTION_UP: 用户抬起手指的事件。: 用户在屏幕上移动手指的事件。: 事件被取消,例如由于系统的其他操作。: 当已经有一个手指按下的情况下,另一个手指按下会产生该事件;: 多个手指同时按下的情况下,抬起其中一个手指会产生该事件;

weijiangbc0的博客 1455

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

安卓View—事件分发机制(二)

安卓View—事件分发机制 文章目录安卓View—事件分发机制一、基础知识1.事件分发是什么2.点击事件的传递规则3.事件分发的顺序二、事件分发源码分析1.Activity对点击事件的分发过程2.ViewGroup对点击事件的分发过程3.View对点击事件的分发过程三、事件分发总结 一、基础知识 1.事件分发是什么 事件分发就是对MotionEvent事件的分发过程,即当一个MotionEvent...

睡客的博客 1752

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

安卓view事件分发机制

安卓view事件分发机制

似霰的博客 981

解锁Android View事件分发机制:从原理到实战

在 Android 中,用户与界面的交互操作,如点击、滑动、长按等,都会产生触摸事件,这些事件被封装成MotionEvent对象。MotionEvent包含了丰富的信息,如触摸的坐标、触摸的类型、触摸的时间等,开发者可以通过这些信息来处理用户的操作。:手指刚接触屏幕时触发,这是一个事件序列的开始。在点击操作中,当手指按下屏幕的瞬间,就会产生ACTION_DOWN事件。它是事件处理的起点,后续的其他事件往往基于此展开。

顾林海 4551

Android View事件分发机制解析

Android View事件分发机制解析

m0_54057522的博客 888

Android View事件分发机制详解

事件分发遵循一个自顶向下传递,再自底向上回溯的过程,就像一个包裹从公司前台(顶层 View)开始,一层层向下传递到可能签收的部门(具体 View),如果没人签收就一层层退回来。)事件的核心流程,它决定了触摸事件如何从系统传递到具体的 View 并被消费。掌握 Android View 事件分发机制是成为熟练 Android 开发者的重要一步,尤其是在处理复杂 UI 交互时。Android 的 View 事件分发机制是处理用户触摸(既是容器也是 View,具有拦截事件的能力。),每层是一个部门 (

JMY_2016的博客 1332

Android View(二)——View的事件分发机制

文章目录一.Activity的层级结构二.事件分发的基础认识1.事件分发是什么2.事件分发过程中的三个重要方法二.事件分发的过程了解1.图解:1.1基本过程图解1.2 关于ACTION_MOVE 和 ACTION_UP2. demo验证上述过程三.onTouch、onClick、onLongClick的调用顺序四.事件分发源码分析1.Activity的事件分发2.ViewGroup的事件分发机制3.View的事件分发机制五.事件分发的总结六.参考资料 一.Activity的层级结构 在了解View的事件分发

haazzz的博客 2094

一文读懂Android View事件分发机制

Android View 虽然不是四大组件,但其并不比四大组件的地位低。而View的核心知识点事件分发机制则是不少刚入门同学的拦路虎。ScrollView嵌套RecyclerView(或者ListView)的滑动冲突这种老大难的问题的理论基础就是事件分发机制。 事件分发机制面试也会经常被提及,如果你能get到要领,并跟面试官深入的灵魂交流一下,那么一定会让面试官对你印象深刻,

学无止尽,生命不息.. 1万+

安卓view事件的分发和拦截机制

Android View事件分发机制详解:事件从Activity开始,依次经过Window、DecorView传递到ViewGroup和子View。核心方法包括dispatchTouchEvent(事件分发)、onInterceptTouchEvent(仅ViewGroup有,用于拦截)和onTouchEvent(事件处理)。ViewGroup分发时优先检查拦截,未拦截则分发给子View;子View处理则流程终止,否则由ViewGroup处理。OnTouchListener优先于onTouchEvent执行

weixin_41119184的博客 975
上一篇: Activity 的 Flags 和 IntentFilter 匹配规则
下一篇: Android View measure layout draw 过程解析
CoderYue
博客等级 码龄11年 201粉丝 93原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值