Android的touch事件分发的源码解析

本文详细解析了Android中Touch事件的分发机制,包括View和ViewGroup如何处理触摸事件,以及事件如何从Activity传递到具体的View。文章还讨论了事件拦截与消费的过程,并给出了示例说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

开发中总会遇到一些滑动事件的冲突,还有自定义控件一些touch事件的设置,所以一直困扰于Touch事件的分发,这两天终于忍着恶心看完了Touch事件分发机制的源码,浅显见识,权当笔记记录。                                            一句话粗糙总结就是:分发是从父类向子类分发,子类消费的话父类不再执行除非拦截,子类不消费的话还回传给父类执行。以下是源码分析。Touch事件分发主要涉及到View和ViewGroup,ViewGroup继承自View,但是分发过程比View复杂。
View中涉及到两个方法,dispatchTouchEvent和onTouchEvent,先看dispatchTouchEvent的源码:
/** Pass the touch screen motion event down to the target view, or this
 * view if it is the target.
 *  把触摸屏幕的动作传递到目标view,如果这个view就是目标view的就传给它自己
 * @param event The motion event to be dispatched.   要分发的动作事件
 * @return True if the event was handled by the view, false otherwise. 如果这个事件被这个     *view处理并消费了就返回true,反之返回false
 */
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;//初始结果设为false不消费
    //mInputEventConsistencyVerifier  用于调试目的的一致性校验,不为空去执行该对象的onTouchEvent,但不消费事件
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        //如果是down事件,为新手势做准备的清理工作,该方法看注释说是停止嵌套的scroll滑动
        stopNestedScroll();
    }

    if (onFilterTouchEventForSecurity(event)) {//该方法注释 过滤触摸事件以应用安全策略,当window窗口被占用时,放弃掉这个touch事件
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {//第一个判断条件是按钮enable,第二个判断条件注释为 处理被鼠标头洞的滑动条,如果事件被当做鼠标拖拽的滑动条处理就返回true。整体看按钮enable但是被鼠标拖动的滑动条,返回true,事件被消解
            result = true;
        }
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;//监听信息赋值
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            //监听事件不为空,并且touchlistener不为空,并且按钮enable,并且touchlistener中的touch方法返回了true消费了事件,则返回true,事件被消费
            result = true;
        }

        if (!result && onTouchEvent(event)) {//result为false事件没被消费那么执行该view的onTouchEvent方法,如果onTouchEvent事件返回true则消费事件,整体返回true,事件被消费
            result = true;
        }
    }

    if (!result && mInputEventConsistencyVerifier != null) {//result为false,调试也不处理
        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.
    //事件的结尾,或者事件取消或者down之后并没有处理,清除
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}
上面的代码核心的点就是39行到48行,总结起来就是:如果设置了touchlistener并且该view为enable的,就执行touchlistener的ontouch方法。如果ontouch方法处理了事件并且返回了true,不继续执行,事件被消费;如果touchlistener为null,或者touchlistener不为null但是ontouch方法返回false,则去执行该view的onTouchEvent方法,onTouchEvent方法处理了事件并且返回true则事件在这里被消费。注意down,move,up事件是分开的,一步一步来的。                                                            再看onTouchEvent方法源码:
/* * Implement this method to handle touch screen motion events.
 * <p>
 * If this method is used to detect click actions, it is recommended that
 * the actions be performed by implementing and calling
 * {@link #performClick()}. This will ensure consistent system behavior,
 * including:
 * obeying click sound preferences
 * dispatching OnClickListener calls
 * handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
 * accessibility features are enabled
 * @param event The motion event.
 * @return True if the event was handled, false otherwise.
 */
public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();
    if ((viewFlags & ENABLED_MASK) == DISABLED) {//可点击的disable的view仍然消费这个touch事件,只是对事件没有回应(不是重点)
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        // A disabled view that is clickable still consumes the touch
        // events, it just doesn't respond to them.
        return (((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
    }
    if (mTouchDelegate != null) {//如果view有代理,则执行代理的onTouchEvent事件
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
            (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {//如果view可点击,或者view可长按,或者view背景可点击,只要满足任一个条件,即进入下面处理并且总是在最后返回true
        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);
                   }

                    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.
				//不是直接调用performClick而是使用线程并且post出去
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {//没有post执行就直接去执行performClick();方法
                                performClick();//该方法调用了clicklistener中的onclick方法
                            }
                        }
                    }

                    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:
                mHasPerformedLongPress = false;

                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
			//不是在正在滑动的容器中,设置是按下,并检查是不是长按,如果按下500ms则判断为长按
                    setPressed(true, x, y);//点击
                    checkForLongClick(0, x, y);//长按检查
                }
                break;

            case MotionEvent.ACTION_CANCEL:
                setPressed(false);
                removeTapCallback();
                removeLongPressCallback();
                mInContextButtonPress = false;
                mHasPerformedLongPress = false;
                mIgnoreNextUpEvent = false;
                break;

            case MotionEvent.ACTION_MOVE:
                drawableHotspotChanged(x, y);

                // Be lenient about moving outside of buttons
                if (!pointInView(x, y, mTouchSlop)) {//挪出按钮取消点击和长按反馈
                    // Outside button
                    removeTapCallback();
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        // Remove any future long press/tap checks
                        removeLongPressCallback();

                        setPressed(false);
                    }
                }
                break;
        }

        return true;
    }

    return false;
}
private void checkForLongClick(int delayOffset, float x, float y) {
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
            mHasPerformedLongPress = false;
            if (mPendingCheckForLongPress == null) {
                mPendingCheckForLongPress = new CheckForLongPress();
            }
            mPendingCheckForLongPress.setAnchor(x, y);
            mPendingCheckForLongPress.rememberWindowAttachCount();
            postDelayed(mPendingCheckForLongPress,
                    ViewConfiguration.getLongPressTimeout() - delayOffset);
        }
    }
private final class CheckForLongPress implements Runnable {
        private int mOriginalWindowAttachCount;
        private float mX;
        private float mY;

        @Override
        public void run() {
            if (isPressed() && (mParent != null)
                    && mOriginalWindowAttachCount == mWindowAttachCount) {
                if (performLongClick(mX, mY)) {
                    mHasPerformedLongPress = true;
                }
            }
        }

        public void setAnchor(float x, float y) {
            mX = x;
            mY = y;
        }

        public void rememberWindowAttachCount() {
            mOriginalWindowAttachCount = mWindowAttachCount;
        }
    }
public boolean performLongClick(float x, float y) {
        mLongClickX = x;
        mLongClickY = y;
        final boolean handled = performLongClick();
        mLongClickX = Float.NaN;
        mLongClickY = Float.NaN;
        return handled;
    }
public boolean performLongClick() {
        return performLongClickInternal(mLongClickX, mLongClickY);
    }
private boolean performLongClickInternal(float x, float y) {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);

        boolean handled = false;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLongClickListener != null) {
            handled = li.mOnLongClickListener.onLongClick(View.this);
        }
        if (!handled) {
            final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y);
            handled = isAnchored ? showContextMenu(x, y) : showContextMenu();
        }
        if (handled) {
            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
        }
        return handled;
    }
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);
        return result;
    }
  以上代码的核心点是,是可点击的控件都会处理这个事件,并且每次事件都返回true。
1,down事件时会判断是否只是单纯的点击,并且检测是不是长按事件,标示是mHasPerformedLongPress,查看checkForLongClick方法源码看到CheckForlongPress类源码可知,当mOnLongClickListener不为空,并且它的onLongClick方法返回true时mHasPerformedLongPress才赋值为true;                                                           
2,move的时候检查触摸事件是否移出了这个view,如果移出了则取消点击和长按的检测反馈处理;                       3,up的时候如果移出了则不处理,如果没有移出,是点击事件并且mHasPerformedLongPress为false,也就是onLongClick方法返回false,则会执行onclicklistener监听的onclick方法,看performClick方法源码就是。总体来看短促的点击事件一定会执行onclicklistener监听的onclick方法,当长按的时候,如果onLongClick不返回true,长按和短按事件可以同时响应。                                    总结view的分发其实就是如果控件是enable的并且有ontouchlistener就执行ontouchlistener的ontouch方法。如果ontouch返回true则结束dispatchTouchEvent返回true,事件被消费;如果ontouch返回false就去执行ontouchEvent。如果ontouchevent返回true,结束且dispatchTouchEvent返回true事件被消费;如果返回false则dispatchTouchEvent返回false。注意ontouchevent的up时会在恰当的时机回调点击事件和长按事。      ViewGroup涉及到三个方法,dispatchTouchEvent,onInterceptionTouchEvent,onTouchEvent。onInterceptionTouchEvent用于事件拦截,默认返回false不拦截;dispatchTouchEvent会调用onInterceptionTouchEvent方法判断该ViewGroup是否拦截事件,想要拦截事件的话可重写该方法,在适当的条件下返回true拦截事件;onTouchEvent方法完全继承自父类也就是view的,不再赘述。主要分发逻辑在dispatchTouchEvent方法中,源码如下:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
    }

    // If the event targets the accessibility focused view and this is it, start
    // normal event dispatch. Maybe a descendant is what will handle the click.
    if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
        ev.setTargetAccessibilityFocus(false);
    }

    boolean handled = 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.
            //down事件时,清空之前所有的状态
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        // Check for interception.
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {//在down事件,或者mFirstTouchTarget即目标target之前给赋值了不为空了(事件被子类消费的话mFirstTouchTarget会被赋值,参见后面的代码)
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//查看是否不允许被拦截
            if (!disallowIntercept) {//不许拦截字段disallowIntercept为false,该值由requestDisallowInterceptTouchEvent方法赋值,一般由子类调用,用于强制父类不拦截触摸事件。为false表示ViewGroup可以拦截,那么调用onInterceptionTouchEvent方法看事件是否被拦截,这里如果重写onInterceptTouchEvent方法,可以实现,down事件被子view消费,但是move和up事件可以被拦截,交给父类处理。
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {//else就是disallowIntercept==true,表示不允许被父类拦截,intercepted只能为false
                intercepted = false;
            }
        } else {//不是down事件如果是up或者move,cancel事件时,且mFirstTouchTarget 为空没有被赋值,说明事件在上一波事件时已经被拦截了,intercepted置为true
            // There are no touch targets and this action is not an initial down
            // so this view group continues to intercept touches.
            intercepted = true;
        }

        // If intercepted, start normal event dispatch. Also if there is already
        // a view that is handling the gesture, do normal event dispatch.
        if (intercepted || mFirstTouchTarget != null) {
            ev.setTargetAccessibilityFocus(false);
        }

        // 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) {//事件没有被取消也没有被拦截
            // If the event is targeting accessiiblity 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) {//ACTION_POINTER_DOWN是多点触摸按下的事件
                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;
                if (newTouchTarget == null && childrenCount != 0) {//新的触摸target不为null并且子view数量不为0,为0的话没有子类就不用分发了,自己消费就消费,不消费就返给父类了。
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);
                    // Find a child that can receive the event.找到能收到事件的子类,就是触摸的点所在的控件范围
                    // Scan children from front to back. 从头到尾浏览子类
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
		    //轮询子类,看子view是否消费该事件
                    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)) {
                         //子view不能收到触摸事件或者子类不在触摸范围内,则continue轮询下一个子view
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }
                        //能走到这里说明该子view能收到触摸事件并且在触摸范围内,
                        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.
                            //如果该方法为true表示子类消费了该事件,查看dispatchTransformedTouchEvent具体实现会发现,当child不为null 的时候就执行child的dispatchTouchEvent,否则执行父类的dispatchTouchEvent。后附dispatchTransformedTouchEvent方法的源码
                            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();
                            //addTouchTarget方法将child赋值给target.child, 并将target赋值给mFirstTouchTarget ,并且返回target赋值给newTouchTarget,看addTouchTarget方法实现即可知如此。后附addTouchTarget方法的源码
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            //alreadyDispatchedToNewTouchTarget 赋值为true,之后的代码中要用到
                            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.
        if (mFirstTouchTarget == null) {//如果mFirstTouchTarget 为null说明没有找到子view消费该事件,那么执行父类(也就是view)的dispatchTouchEvent方法,并返回父类处理的结果。
            // No touch targets so treat this as an ordinary view.
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {//如果mFirstTouchTarget 不为null说明有子view消费了该事件,
            // 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;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                //呼应上面down的事件,down事件有子view消费事件,这里会返回true
                    handled = true;
                } else {//不是down事件 ,子view消费这个事件的话也会返回true
                    //这里有判断是否被拦截了,如果事件被拦截了,intercepted为true,cancelChild为true。
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {//cancelChild做参数,事件没有拦截且子类消费了返回true,handler赋值为true。如果被拦截了则回去执行父类(也就是view)的dispatchTouchEvent方法。
                        handled = true;
                    }
                    if (cancelChild) {//事件被拦截的话,target置为null
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }

        // Update list of touch targets for pointer up or cancel, if needed.
        if (canceled
                || actionMasked == MotionEvent.ACTION_UP
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            resetTouchState();
        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
            final int actionIndex = ev.getActionIndex();
            final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
            removePointersFromTouchTargets(idBitsToRemove);
        }
    }

    if (!handled && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    }
    return handled;
}
/**
     * 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;
        }

        // Calculate the number of pointers to deliver.
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        // If for some reason we ended up in an inconsistent state where it looks like we
        // might produce a motion event with no pointers in it, then drop the event.
        if (newPointerIdBits == 0) {
            return false;
        }

        // If the number of pointers is the same and we don't need to perform any fancy
        // irreversible transformations, then we can reuse the motion event for this
        // dispatch as long as we are careful to revert any changes we make.
        // Otherwise we need to make a copy.
        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }

        // Perform any necessary transformations and dispatch.
        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;
    }
/**
     * 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;
    }
总结viewGroup的dispatchTouchEvent方法的核心是,先看是否被父view拦截,没有被拦截的话 轮询在触摸区域内的子view,执行子view的dispatchTouchEvent,子view也会轮询自己的子view,直到最后一个子view,如果子view消费了该事件,子类的dispatchTouchEvent方法返回true,父view不再消费事件,如果子 view都没有消费则返回给父view消费该事件。                                 一步步来,分好几种情况:                                                      1,从down事件开始被拦截,看dispatchTouchEvent源码在30行代码进入,然后被拦截,intercepted为true,然后在59行代码进不去,此时mFirstTouchTarget是null,此时从168行进入判断,执行父类的dispatchTouchEvent。那么之后再过来的move和up事件,在30行进不去,导致intercepted为true,同样在59行代码进不去,此时mFirstTouchTarget为null,则同样从168行代码进入,执行父类的dispatchTouchEvent。                    2,从down事件进入不拦截,intercepted为false,然后在30行代码进入执行,最终会轮询所有触摸到的子view,如果没有子view消费事件则同第一种情况之后事件都由父类处理;如果有子view消费该事件,mFirstTouchTarget,newTouchTarget不为null,alreadyDispatchedToNewTouchTarget均为true,在172行进入,并且在179行进入,返回true。之后再来move和up事件的时候,因为mFirstTouchTarget不为null会从30行进入,此时也有两种情况,情况a,在move或者up事件时不拦截,intercepted为false,从59行进入,然后68行进不去所以基本又出来了,然后在172进入,在184处cancelChild为false,在186处执行子view的dispatchTouchEvent,若子view消费则handler置为true,最终会返回true;否则最终会返回false。可见子view消费过down事件后是可以不消费move和up事件的。情况b,在move或者up事件时拦截,intercepted为true,59行进不去,在172行进入,在184处cancelChild为true,那么在186行代码处,看dispatchTransformedTouchEvent方法的实现,由于cancelChild为true,事件被设置为cancel,此时child不为null,执行子view的dispatchTouchEvent方法,但是是cancel所以返回false,会执行父类的dispatchTouchEvent方法。可见子view消费down事件后,如果父view拦截的话,子view将不能执行move和up事件。
从源头说起,当手指触摸屏幕的时候,首先事件会传递给Activity,执行Activity的dispatchTouchEvent方法
/**
 * 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);
}
  可知触摸事件通过getwindow().superDispatchTouchEvent(ev)方法被分发,getwindow返回的是Window的实例mWindow,看源码mWindow初始化的是Phonewindow:
/**
 * Retrieve the current {@link android.view.Window} for the activity.
 * This can be used to directly access parts of the Window API that
 * are not available through Activity/Screen.
 *
 * @return Window The current window, or null if the activity is not
 *         visual.
 */
public Window getWindow() {
    return mWindow;
}
在Activity源码6619行找到:mWindow = new PhoneWindow(this, window); 而Phonewindow的superDispatchTouchEvent()源码如下:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}
  由此可见,Activity接收到触摸事件,传递给PhoneWindow分发,PhoneWindow又将事件传递给DecorView分发,查看DecorView的源码,知setcontentview设置的layout就是DecorView的一个子类,由此,Activity最终将触摸事件传递给自己的布局view一层层传递分发。如果子类都没有消费事件的话,转而会执行Activity的onTouchEvent(ev)方法,如果onTouchEvent方法也不消费的话就return false,没有任何事情发生。
  下节讲两个简单的事件分发的应用:简单滑动冲突的解决。
P个S:谁能跟我讲讲这个优快云的排版是什么鬼,为啥编辑状态下的排版跟预览不一样啊,哭/(ㄒoㄒ)/~~
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值