深入源码理解Android事件分发机制

深入源码理解Android事件分发机制

如果对Android事件分发机制不理解,可以首先看图解 Android 事件分发机制
在这里插入图片描述

  1. 对于 dispatchTouchEvent,onTouchEvent,return true是终结事件传递。return false 是回溯到父View的onTouchEvent方法。
  2. ViewGroup 想把自己分发给自己的onTouchEvent,需要拦截器onInterceptTouchEvent方法return true 把事件拦截下来。
  3. ViewGroup 的拦截器onInterceptTouchEvent 默认是不拦截的,所以return super.onInterceptTouchEvent()=return false;
  4. View 没有拦截器,为了让View可以把事件分发给自己的onTouchEvent,View的dispatchTouchEvent默认实现(super)就是把事件分发给自己的onTouchEvent。

下面来将深入源码理解Android事件分发机制,首先看下ViewGroup下面的dispatchTouchEvent()事件

事件分发,从按下DOWN事件开始,从ViewGroup.dispatchTouch()往下分发

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);
    // 初始化所以touch状态,主要 mFirstTouchTarget = null;
    resetTouchState();
}

ACTION_DOWN事件,会调用resetTouchState()函数,这个函数主要设置mFirstTouchTarget == null

for (int i = childrenCount - 1; i >= 0; i--) {
    final int childIndex = getAndVerifyPreorderedIndex(
            childrenCount, i, customOrder);

    ...

    newTouchTarget = getTouchTarget(child);

    ...

    // 主要运行child.dispatchTouchEvent()方法,判断自己是否需要需要继续分发
    // ViewGroud继续运行child.dispatchTouchEvent()方法,直到底部为View为止
    // View.dispatchTouchEvent(),首先判断onTouch,再判断onTouchEvent
    // 必须是child.dispatchTouchEvent() = true,不能是孙子View
    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {

        ...

        mLastTouchDownX = ev.getX();
        mLastTouchDownY = ev.getY();
        // Adds a touch target for specified child to the beginning of the list.
        // 把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);
}

ACTION_DOWN事件,会进入for循环遍历子View,把Down事件往下分发,dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)事件,其中childe不为null,
所以dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign) == child.dispatchTouchEvent(),把Down事件分发到childView中,主要分两种情况。

1. 子View消费了事件

子View消费了事件,child.dispatchTouchEvent() == true(在这里假设子View的onTouchEvent()消费事件)
所以,dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)也返回true
调用addTouchTarget(child, idBitsToAssign),把child保存在mFirstTouchTarget中,mFirstTouchTarget != null

 if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    // 运行super.dispatchTouchEvent(),也就是View.dispatchTouchEvent()
    // handled返回dispatchTouchEvent()事件结果,如果未消费为false
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
} else { ... }

因为mFirstTouchTarget != null,就不调用dispatchTransformedTouchEvent(),也即不调用View.dispatchTouchEvent()

看一下View.dispatchTouchEvent()里面主要做了什么
主要先调用onTouch()方法,如果不返回true,在调用onTouchEvent()

ListenerInfo li = mListenerInfo;
// 首先执行onTouch()事件
if (li != null && li.mOnTouchListener != null
        && (mViewFlags & ENABLED_MASK) == ENABLED
        && li.mOnTouchListener.onTouch(this, event)) {
    result = true;
}

// 然后执行onTouchEvent()事件
if (!result && onTouchEvent(event)) {
    result = true;
}

所以GroupView的onTouch和onTouchEvent都不会执行了,也即到了子View的onTouchEvent事件就截止了,不会往上继续回调父View的onTouchEvent事件

TouchTarget target = mFirstTouchTarget;
while (target != null) {
    final TouchTarget next = target.next;
    // Down事件mFirstTouchTarget != null的话,alreadyDispatchedToNewTouchTarget = true
    // Up事件mFirstTouchTarget != null的话,alreadyDispatchedToNewTouchTarget = false
    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
        handled = true;
    } else {
        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                || intercepted;
        // Up事件,直接运行上次处理Down事件的child.dispatchTouchEvent()
        // 并且handled = true, 直接消费,不会继续往下分发
        if (dispatchTransformedTouchEvent(ev, cancelChild,
                target.child, target.pointerIdBits)) {
            handled = true;
        }
        ...
    }
}

因为alreadyDispatchedToNewTouchTarget == true,进入if判断里面,设置handled = true,ViewGroup.dispatchTouchEvent()返回true。
activity的也会记录ViewGroup到mFirstTouchTarget里

2. 子View不消费事件

子View不消费事件,child.dispatchTouchEvent() == false

 if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    // 运行super.dispatchTouchEvent(),也就是View.dispatchTouchEvent()
    // handled返回dispatchTouchEvent()事件结果,如果未消费为false
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
} else { ... }

因为前面调用了resetTouchState()函数,所以mFirstTouchTarget==null,所以调用dispatchTransformedTouchEvent(ev, canceled, null, xx)方法,而
因为child == null,调用super.dispatchTouchEvent(transformedEvent),也就是View.dispatchTouchEvent(),就会执行GroupView的onTouch和onTouchEvent事件,往上继续回调父View的onTouchEvent事件

接着UP事件,从ViewGroup.dispatchTouch()往下分发

UP事件,不进入for循环遍历,所以不会再循环执行子View的dispatchTouchEvent()事件

1. DOWN事件,子View消费了事件

mFirstTouchTarget != null,进入else判断里面

TouchTarget target = mFirstTouchTarget;
while (target != null) {
    final TouchTarget next = target.next;
    // Down事件mFirstTouchTarget != null的话,alreadyDispatchedToNewTouchTarget = true
    // Up事件mFirstTouchTarget != null的话,alreadyDispatchedToNewTouchTarget = false
    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
        handled = true;
    } else {
        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                || intercepted;
        // Up事件,直接运行上次处理Down事件的child.dispatchTouchEvent()
        // 并且handled = true, 直接消费,不会继续往下分发
        if (dispatchTransformedTouchEvent(ev, cancelChild,
                target.child, target.pointerIdBits)) {
            handled = true;
        }
        ...
    }
}

因为DOWN事件已经记录子View,消费了事件,并且记录在mFirstTouchTarget链表里,UP事件只会执行mFirstTouchTarget链表里的所有子View的事件分发,而不执行GroupView的onTouch和onTouchEvent事件。
所以这个时候UP事件,不管子View的onTouchEvent事件返不返回false,都不会往上继续回调父View的onTouchEvent事件

2. DOWN事件,子View不消费事件

if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
    ...
} else {
    // There are no touch targets and this action is not an initial down
    // so this view group continues to intercept touches.
    intercepted = true;
}

UP事件,而且mFirstTouchTarget == null,所以直接拦截,肯定不会走for遍历循环。
mFirstTouchTarget == null, 调用dispatchTransformedTouchEvent(ev, canceled, null, xx)方法,而
因为child == null,调用super.dispatchTouchEvent(transformedEvent),也就是View.dispatchTouchEvent(),就会执行GroupView的onTouch和onTouchEvent事件,也不会往上继续回调父View的onTouchEvent事件

View.dispatchTouchEvent()完整源码分析

public boolean dispatchTouchEvent(MotionEvent ev) {
    
    ...

    boolean handled = false;
    // Filter the touch event to apply security policies
    // 这里过滤判断里面 是判断窗口是否被遮挡,如果被遮挡则终止处理返回false
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        // 触摸的动作:按下,抬起,滑动,多点按下,多点抬起
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // 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);
            // 初始化所以touch状态,主要 mFirstTouchTarget = null;
            resetTouchState();
        }

        // Check for interception.
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            // 判断是否不允许拦截
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                // onInterceptTouchEvent获知是否拦截
                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;
        }

        ...

        // Check for cancelation.
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        // Update list of touch targets for pointer down, if needed.
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        if (!canceled && !intercepted) {

            ...

            // 一般只有MotionEvent.ACTION_DOWN的时候进入
            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);

                        ...

                        newTouchTarget = getTouchTarget(child);

                        ...

                        // 主要运行child.dispatchTouchEvent()方法,判断自己是否需要需要继续分发
                        // ViewGroud继续运行child.dispatchTouchEvent()方法,直到底部为View为止
                        // View.dispatchTouchEvent(),首先判断onTouch,再判断onTouchEvent
                        // 必须是child.dispatchTouchEvent() = true,不能是孙子View
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {

                            ...

                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            // Adds a touch target for specified child to the beginning of the list.
                            // 把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;
                }
            }
        }

        // Dispatch to touch targets.
        // Down事件如果,dispatchTouchEvent返回true了,mFirstTouchTarget != null,就不调用dispatchTransformedTouchEvent()
        // Up事件如果childView,dispatchTouchEvent返回True了,mFirstTouchTarget != null
        if (mFirstTouchTarget == null) {
            // No touch targets so treat this as an ordinary view.
            // 运行super.dispatchTouchEvent(),也就是View.dispatchTouchEvent()
            // handled返回dispatchTouchEvent()事件结果,如果未消费为false
            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;
                // Down事件mFirstTouchTarget != null的话,alreadyDispatchedToNewTouchTarget = true
                // Up事件mFirstTouchTarget != null的话,alreadyDispatchedToNewTouchTarget = false
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    // Up事件,直接运行上次处理Down事件的child.dispatchTouchEvent()
                    // 并且handled = true, 直接消费,不会继续往下分发
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }

        // Update list of touch targets for pointer up or cancel, if needed.
        // Up事件重置Touch事件状态
        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);
        }
    }

    ...
    return handled;
}

其中的dispatchTransformedTouchEvent()事件主要用来执行dispatchTouchEvent()事件

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

    ...

    // child == null,运行View.dispatchTouchEvent()
    // child != null,运行child.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());
        }

        handled = child.dispatchTouchEvent(transformedEvent);
    }

    // Done.
    transformedEvent.recycle();
    return handled;
}

再来看下View下面的dispatchTouchEvent()事件

public boolean dispatchTouchEvent(MotionEvent event) {

    ...

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

        // 然后执行onTouchEvent()事件
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    ...

    return result;
}

最后看下View下的onTouchEvent()事件

public boolean onTouchEvent(MotionEvent event) {

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

    ...

    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:

                ...

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


                    // 如果不是长按事件,执行点击事件performClick()
                    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();
                            }
                        }
                    }

                }
                mIgnoreNextUpEvent = false;
                break;

            case MotionEvent.ACTION_DOWN:
                
                mHasPerformedLongPress = false;

                // 如果不可点击,判断是否是长按事件,直接返回
                if (!clickable) {
                    checkForLongClick(0, x, y);
                    break;
                }

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

                // checkForLongClick():postDelay(长按事件)
                // 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;
}

结论

1. onTouch()和onTouchEvent()的区别

该2个方法都是在View.dispatchTouchEvent()中调用
但onTouch()优先于onTouchEvent()执行;若手动复写在onTouch()中返回true(即将事件消费掉),将不会再执行onTouchEvent()

2. onClick()和onLongClick()

该2个方法都是在View.onTouchEvent()中调用
和onLongClick()优先于onClick()执行,以为在Down事件里面就会发送postDealy(onLongClick()),Up如果不执行onLongClick()才执行onClick()事件

3. Touch事件的后续事件(MOVE、UP)层级传递

当dispatchTouchEvent()事件分发时,只有前一个事件(如ACTION_DOWN)返回true,才会收到后一个事件(ACTION_MOVE和ACTION_UP)

即如果在执行ACTION_DOWN时返回false,后面一系列的ACTION_MOVE、ACTION_UP事件都不会执行

ACTION_DOWN事件在哪个控件消费了(return true), 那么ACTION_MOVE和ACTION_UP就会从上往下(通过dispatchTouchEvent)做事件分发往下传,就只会传到这个控件,不会继续往下传

如果ACTION_DOWN事件是在dispatchTouchEvent消费,那么事件到此为止停止传递,如果ACTION_DOWN事件是在onTouchEvent消费的,那么会把ACTION_MOVE或ACTION_UP事件传给该控件的onTouchEvent处理并结束传递。

可以看图示如下图示(图片来自图解 Android 事件分发机制)

在这里插入图片描述

在ViewGroup2的dispatchTouchEvent返回false,并且在ViewGroup1的onTouchEvent返回true消费这次事件
红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向

发布了29 篇原创文章 · 获赞 3 · 访问量 1131

猜你喜欢

转载自blog.csdn.net/qq_16927853/article/details/102761482
今日推荐