Android自定义View之Touch事件分发机制源码解析

在Android系统中View的结构为树形结果,那么就会出现上层的view盖住下层的view的情况发生,而当我们点击上层View的时候,这个时候是上层的View还是盖住的下层的View可以消费这个touch事件呢?

而为了解决这个问题,就引出了Android中的Touch事件的分发机制.

而在Android中Touch事件的分发又分为View和ViewGroup的事件分发,由此我们先从简单的View的touch事件分发讲起:

首先,我们知道一次完整的Touch事件序列为:

   ACTION_DOWN    ---->     ACTION_MOVE    ---->     ACTION_UP /  ACTION_CANCEL(人为取消的情况)

而对于Touch事件的分发,不管是View还是ViewGroup都和一下的三个方法有关系:

   dispatchTouchEvent():事件分发

   onInterceptTouchEvent():事件拦截(只有ViewGroup才有该方法)

   onTouchEvent():事件消费

1. dispatchTouchEvent 分发触摸事件  onInterceptTouchEvent() onTouchEvent()方法都是由dispatchTouchEvent()方法进行调用的
2. onInterceptTouchEvent 拦截触摸事件  表示是否拦截touch事件,true表示拦截 反之false表示不拦截
3. onTouchEvent 处理触摸事件 true表示消费事件,false表示没有消费

View的dispatchTouchEvent()方法源码分析如下:

//1.首先View的事件分发过程是从dispatchTouchEvent()方法开始的
//view的父容器将touch事件传递给View的dispatchTouchEvent()方法
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);
        }
        
    //2.定义一个变量表示当前touch事件是否被该view消费
        boolean result = false;

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

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

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            //3.首先判断li是否为空 一般情况是不为空null li是view的一个内部类里面定义了一些常用监听器接口的实例如 OnClickListener OnLongClickListener onTouchListener等....
            //再判断li.mOnTouchListener是否为空如果我们对view设置了setOnTouchListener(new OnTouchListener())的话该变量也不会为空
            //然后判断View是否为激活Enabled的状态
            //最后调用我们设置的OnTouchListener()监听器的onTouch()方法并根据
            //OnTouchListener()的onTohch()方法的返回值来决定是否 消费这次的touch事件
            //如果onTouchListener()的onTouch()方法返回了true直接就返回true表示消费这次的touch事件,不再继续往下面走了
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            
            //4.如果没有对View设置OnTouchListener()或者View为disable状态或者设置的OnTouchListener()的onTouch()方法返回false
            //那么会去调用View的onTouchEvent()方法 由View的onTouchEvent()方法的返回值来决定是否消费此次touch事件
            if (!result && onTouchEvent(event)) {
                //如果onTouchEvent()方法返回true直接返回true表示消费了这次的touch事件 不在继续往下面走了
                result = true;
            }
        }

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

        // Clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
        // of the gesture.
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }
        
        //如果OnTouchListener()的onTouch()方法返回false(即onTouchListener的onTouch()方法没有消费touch事件)
        //同时View的onTouchEvent()方法也返回false表示view自身没有消费这次touch事件那么就直接返回result=false表示该view没有消费这次touch事件
        //会将该touch事件交给其父容器的onTouchEvent()进行处理
        return result;
    }

View的onTouchEvent()方法源码分析如下:

//1.view的onTouchEvent()方法由view的dispatchTouchEvent()方法进行调用
public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();
        
    //2.首先判断view的是否可点击cilck
    //通过CLICKABLE LONG_CLICKABLE CONTEXT_CLICKABLE三个flag来决定cliable的值
    //以上的三个分别表示view是否可以点击以及长点击
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        //3.判断view是否是DISABLED未激活的状态
        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.
            //如果View是未激活DISABLED的状态,但是只要该view的viewFlags 为CLICKABLE LONG_CLICKABLE CONTEXT_CLICKABLE中的一种的话
            //那么该view还是可以消费掉这次touch事件的,只是不会响应我们设置的OnCLickListener的onClick()方法 onLongClickListener的onLongClick()方法等等
            return clickable;
        }
        if (mTouchDelegate != null) {
            //如果设置了TouchDelegate代理对象的话会直接将touch事件交给TouchDelegate对象处理直接返回true
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        
    //如果View的clickable为true即view是可以进行点击的则进入相应的ACTION的判断
        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)) {
                                    //在ACTION_UP中执行了performClick()方法可以看到在performClick()方法中最终执行了OnClickListener的onClick()方法
                                    //由此我们可以得出click点击事件是在ACTION_UP中触发的
                                    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;
            }
            //只要clickable为true即view只要是可点击的就会消费掉touch事件返回true
            return true;
        }
        
        //反之就不消费touch事件返回false
        return false;
    }

View的Touch事件分发流程大致如下:

Android自定义View之Touch事件分发机制源码解析_第1张图片

从View的touch事件传递流程得出以下几点:
1.OnTouchListener()的onTouch()方法是优先于View的OnTouchEvent()方法执行的如果OnTouchListener的onTouch()方法
返回了true表示消费了touch事件那么后续View的onTouchEvent()方法也就不会再执行了那么View的onClick() onLongClick()
等方法也就不会再接着执行了(onClick() onLongClick()等方法都是在onTouchEvnet()方法中进行执行的)
2.如果View是未激活的即处于DISABLED状态但是是可点击的(CLICKABLE LONG_CLICKABLE CONTEXT_CLICKALE)那么view也会
消费掉touch事件但是不会响应OnClickListener的onClick()方法 onLongClickListener的onLongCLick()方法等
3.只要是View可点击的并且处于ENABLED状态那么就一定返回true即一定会消费touch事件
4.在View的onTouchEvent()方法中处理我们常见的点击事件如:ACTION_DOWN 中处理长点击onLongClick()  
  ACTION_UP中处理点击onClick()等
5.onTouch() onTouchEvent()中事件是否被消费了由方法的返回值来决定 而不是由我们是否在方法中使用了
   touch事件MotionEvent来决定的
6.View的事件调度顺序是dispatchTouchEvent() -> onTouchListener() -> onTouchEvent() -> onLongCLick() -> onClick() 
7.View是没有onIterceptTouchEvent()即没有拦截touch事件的方法的,ViewGroup才有
8.View的onTouchEvent()方法中只要view是clickable(CLICKABLE  LONG_CLICKABLE  CONTEXT_CLICKABLE)的那么
   就会消费这次touch事件(从ACTION_DOWN 到 ACTION_UP结束) 
在安卓中有些控件默认是clickable的比如Button checkbox 等 而TextView LinearLayout等是non clickable的

说完View的Touch事件分发,接下类我们就开始将讲讲ViewGroup的Touch事件分发

1.ViewGroup的Touch事件是从Activity(或者通过WindowManager添加的view)的dispatchTouchEvent()方法开始

Activity的dispatchTouchEvent()方法如下:

public boolean dispatchTouchEvent(MotionEvent ev) {           
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            //当touch事件的ACTION_DOWN到达Activity的dispatchTouchEvent()方法时会首先回调该方法(空实现) 我们可以根据业务需要实现一些特定的逻辑
            //比如实现屏保功能,当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法
            onUserInteraction();
        }
    //获取Window接口的实现对象PhoneWindow 调用PhoneWindow对象的superDispatchTouchEvent()方法
        if (getWindow().superDispatchTouchEvent(ev)) {
            //如果superDispatchTouchEvent()方法返回true代表消费了此次touch事件 直接返回true
            return true;
        }
    //反之如果superDispatchTouchEvent()返回false交由Activity的onTouchEvent()方法进行touch事件的处理
        return onTouchEvent(ev);
    }

PhoneWindow的superDispatchTouchEvent()方法如下:

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

PhoneWindow的superDispatchTouchEvent()方法最终会调用DecorView的superDispatchTouchEvent()方法:

public boolean superDispatchTouchEvent(MotionEvent event) {
    //由于DecorView继承自FrameLayout的因此最终会调用ViewGroup的dispatchTouchEvent()方法
    return super.dispatchTouchEvent(event);
}

ViewGroup的dispatchTouchEvent()方法:

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

        //定义一个变量表示是否消费这次的touch事件(ACTION_DOWN -> ACTINO_MOVE -> ACTION_UP)默认是不消费
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
            //处理当MotionEvent为ACTION_DOWN的时候 ACTION_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.
                //当开始一个新的触摸手势时(ACTION_DOWN为开始一个新的触摸手势),必须丢弃掉所有以前的状态。
                //由于应用程序切换、ANR或其他一些状态变化系统可能已经删除了上一个手势的up或cancel事件(UP或者CANCEL为一次完整的touch事件的结束)
                //因此我们需要在ACTION_DOWN即一次全新的touch事件开始时对一些变量进行重置操作
                //最重要的是重置FLAG_DISALLOW_INTERCEPT为false(表示拦截touch事件)
                // 将mFirstTouchTarget置为null  表示当前没有子view消费touch事件
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // Check for interception.
            //检查是否需要拦截touch事件
            final boolean intercepted;
            //如果为ACTION_DOWN 或者mFirstTouchTarget不为null空(即有子view消费touch事件)
            //这里要注意这个mFirstTouchTarget 它是ViewGroup的一个内部类封装了被触摸的view以及这次触摸对应的id主要用于多点触控
            //mFirstTouchTarget贯穿整个dispatchTouchEvent()的流程
            //mFirstTouchTarget不为空表示有子View消费touch事件
            //mFirstTouchTarget为空表示ViewGroup拦截了touch事件或者子view没有消费touch事件总之此时需要viewGroup自己处理touch事件(消费或者不消费)
            //当actionMasked 不为ACTION_DOWN但是有子view消费touch事件时此时任然需要判断是否拦截touch事件(比如ACTION_MOVE ACTION_UP..)
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //获取FLAG_DISALLOW_INTERCEPT的值该值在上面的resetTouchState()方法中(ACTION_DOWN时)进行过重置为false
                //我们知道子view可以通过getParent().requestDisallowInterceptTouchEvent(true)让ViewGroup禁止掉touch事件的拦截
                //但是requestDisallowInterceptTouchEvent()针对ACTION_DOWN 是无效的只针对ACTION_MOVE ACTION_UP等才有效果
                //因为在上面判断actionMasked为ACTION_DOWN时会调用resetTouchState()方法重置FLAG_DISALLOW_INTERCEPT为false
                //因此会走到调用onInterceptTouchEvenet()方法中根据onInterceptTouchEvent()方法的返回值来决定是否拦截touch事件
                //requestDisallowInterceptTouchEvent()方法的本质其实是通过改变flag值来控制最终是否调用onInterceptTouchEvent()方法来实现的
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                //disallowIntercept为false会调用onInterceptTouchEvent()方法根据onInterceptTouchEvent()方法的返回值
                //来决定是否拦截touch事件
                if (!disallowIntercept) {
                    //调用onInterceptTouchEvent()方法来决定是否拦截touch事件
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    //反之disallowIntercept 为true则不进行touch事件的拦截将intercepted的值置为false
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                //如果不是ACTION_DOWN 并且没有子view消费掉touch事件此时直接拦截touch事件
                //意思就是如果ACTION_DOWN 没有被子view消费那么接下来的ACTION_MOVE ACTINO_UP就都不会走onInterceptTouchEvent()方法
                //判断是否需要拦截touch事件了而是直接将intercepted设置为true直接进行拦截交给自身进行touch事件的处理(dispatchTouchEvent() -> onTouch() -> onTouchEvent())
                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.
            //检查cancel状态
            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;
            //如果Touch事件没有被ACTION_CANCEL取消 或者touch事件没有被拦截就开始执行Touch事件的分发
            //注意在if (!canceled && !intercepted) {} 的过程中只针对ACTION_DOWN的分发不针对ACTION_MOVE ACTION_UP的分发
            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;

                //如果为ACTION_DOWN执行ACTION_DOWN的分发
                if (actionMasked == MotionEvent.ACTION_DOWN
                        //ACTION_POINTER_DOWN为多点触控
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

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

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        //计算touch点的坐标根据touch点坐标判断当前触摸到的是哪个子view
                        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.
                        //对可能需要接收touch事件的子view进行一个接收顺序的排序 从前往后 即从最上层往最下层进行排序
                        final ArrayList preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        //注意是从后往前进行遍历 最后面的child是最上层的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)) {
                                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表示当newTouchTarget != null即已经找到了接收touch事件的子view 不用再继续遍历寻找接收touch事件的目标view了
                                //即此时手指已经按在了一个button上(ACTION_DOWN)此时又有一个手指按在了这个button上(ACTION_POINTER_DOWN),那么接收
                                //touch事件的还是这个button只是会给这个button新增一个pointerId而已
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            //在dispatchTransformedTouchEvent()方法中执行touch事件的分发
                            //dispatchTransformedTouchEvent()分发touch事件有两种情况
                            //dispatchTransformedTouchEvent()中调用了child.dispatchTouchEvent()方法
                            //根据child.dispatchTouchEvent()方法的返回值判断子view是否消费了touch事件
                            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();
                                //通过addTouchTarget()方法将消费了touch事件的child view赋值给
                                //mFirstTouchTarget此时mFirstTouchTarget不为null空
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                //标记alreadyDispatchedToNewTouchTarget为true表示此时已经将touch事件分发给了子view即子view消费了touch事件
                                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;
                    }
                }
            }

            //在此处再执行touch事件的分发
            // Dispatch to touch targets.
            //如果mFirstTouchTarget == null表示ViewGroup拦截了touch事件或者在上面遍历寻找接收touch事件的子view
            //没有找到或者找到了接收touch事件的子view但是子view没有消费掉touch事件
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                //如果没有找到接收touch事件的子view那么调用dispatchTransformedTouchEvent()方法
                //在dispatchTransformedTouchEvent()方法内部执行super.dispatchTouchEvent(ev)
                //即将ViewGroup当做一个普通的view调用View的dispatchTouchEvent()方法(ViewGroup继承自View)
                //那么最终的调用顺序是onTouch()(如果设置了OnTouchListener()) -> onTouchEvent()方法由自身来决定是否消费touch事件
                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;
                //mFirstTouchTarget 不为空null 表示已经找到了接收touch事件的child view
                while (target != null) {
                    //遍历TouchTarget链表 获取链表中的每一个目标view
                    final TouchTarget next = target.next;
                    //alreadyDispatchedToNewTouchTarget 表示是否找到了接收touch事件的子view
                    //此时mFirstTouchTarget不为空表示肯定执行过上面的遍历寻找接收touch事件的子view
                    //的流程此时会将alreadyDispatchedToNewTouchTarget置为true同时调用了addTouchTarget()方法将
                    //接收touch事件的子view添加进TouchTarget链表的头部同时将mFirstTouchTarget 指向了newTouchTarget
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        //此时已经在上面的遍历寻找接收touch事件的子view的过程中找到了接收touch事件的子view
                        //同时子view也已经消费了ACTION_DOWN事件因此此时不执行任何操作直接返回true
                        handled = true;
                    } else {
                        //通过intercepted来判断是否拦截取消了touch事件的分发
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        //此时没有执行过上面的遍历寻找接收touch事件的子view的流程,但是此时已经找到了
                        //接收touch事件的子view因此流程是如果子view处理了ACTION_DOWN 事件在接下来的
                        //ACTION_MOVE ACTION_UP事件到来时会跳过上面的寻找接收touch事件的子view的流程
                        //而是直接进入当前的这步流程中执行ACTION_MOVE ACTION_UP事件的分发
                        //因为在上面寻找接收touch事件的子view的流程中一开始就有actionMasked == MotionEvent.ACTION_DOWN 的判断
                        //如果不是ACTION_DOWN事件就不会执行遍历寻找接收touch事件的流程也就不会分发ACTION_MOVE ACTION_UP这些touch事件

                        //如果cancelChild为true即ViewGroup拦截了ACTION_DOWN之后的ACTION_MOVE ACTION_UP等事件
                        //那么在dispatchTransformedTouchEvent()方法中会生成一个ACTION_CANCEL事件分发给子view
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }

                        //同时如果ViewGroup拦截取消了touch事件分发会发送一个ACTION_CANCEL事件给子view的同时
                        //还会将mFirstTouchTarget置为null空,那么当下次touch事件来临时就直接走ViewGroup自身的onTouch() -> onTouchEvent()方法的流程
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

            //在ACTION_UP 手指抬起或者ACTION_CANCEL取消touch事件分发恢复重置一些状态
            // 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);
        }
        //返回handled的值 父容器根据返回的handled的值判断viewgroup/view是否消费掉了touch事件
        return handled;
    }

通过以上的源码分析我们得出ViewGroup的Touch事件分发的流程:

Android自定义View之Touch事件分发机制源码解析_第2张图片

另外Activity View ViewGroup这三者的touch事件的方法区别如下:

  Activity View ViewGroup
dispatchTouchEvent()(事件分发)
onInterceptTouchEvent()(事件拦截)
onTouchEvent()(事件消费)

整个touch事件的传递过程为: Activity.dispatchTouchEvent()  -> PhoneWindow.superDispatchTouchEvent()  -> DecorView.superDispatchTouchEvent() -> ViewGroup.dispatchTouchEvent()  -> View.dispatchTouchEvent() 

而消费过程则相反: View.onTouchEvent() -> ViewGroup.onTouchEvent() -> DecorView.onTouchEvent() -> Activity.onTouchEvent()

ViewGroup中包含多个子view时会将touch事件分配给包含在点击位置处的子view

ViewGroup和子view同时注册了监听器OnClickListener 监听事件由子view进行消费

在一次完整的touch事件(ACTION_DOWN -> ACTION_MOVE -> ACTION_UP)传递过程中,touch事件应该被

同一个view进行消费,全部接受或者全部拒绝

只要接受ACTION_DOWN事件就意味着接受所有的事件,拒绝接受ACTION_DOWN 则不会接受后续的内容

如果当前正在处理的touch事件被上层的view拦截,会接收到一个ACTION_CANCEL,后续事件不会再传递过来

父容器拿到触摸事件,默认不拦截(onInterceptTouchEvent() return false),分发给孩子(dispatchTransformedTouchEvent()),

看孩子是否消费, 孩子不消费,事件又传回父容器(onTouchEvent()),看父容器是否消费。

* 父容器拿到触摸事件,如果ACTION_DWON事件传递下去没有孩子消费,那么后续的事件就不会传了。(没有消费mFirstTouchTarget == null)

* 父容器拿到触摸事件,默认不拦截,分发给孩子,看孩子是否消费,孩子消费,事件传递结束。

* 父容器拿到触摸事件,拦截,事件不会分发给孩子,交给自己是否消费(调用父容器自己的onTouchEvent)

父容器拦截touch事件要分为两种:

1.拦截ACTION_DOWN 直接导致mFirstTouchTarget为null 那么直接调用ViewGroup的父类即View类中的dispatchTouchEvent()

方法即dispatchTouchEvent() -> onTouch() -> onTouchEvent()此时会将ViewGroup当做一个普通的View进行处理

2.拦截ACTION_DOWN之后的ACTION_MOVE ACTION_UP事件如果拦截这些事件中的一个事件会将该事件转换成一个

ACTION_CANCEL给消费了这个事件的子view 因此本次的touch事件是不会 传递给拦截了touch事件的viewgroup的而是

当下次touch事件到来时才会传递给该viewgroup的onTouchEvent()方法来处理并且会将mFirstTouchTarget置为null 当下次

touch事件到来时由于mFirstTouchTatget为Null会直接调用自己的onTouchEvent()方法 而不会在传递个子view了

 

另外还有就是关于setOnTouchListener与 onTouchEvent的关系:

setOnTouchListener的onTouch方法和onTouchEvent()方法都会在我们View的dispatchTouchEvent()方法里面执行

不过onTouch()方法会优先于我们的onTouchEvent()方法而且如果OnTouchListener不为空直接执行onTouch()方法

并且直接返回true就不会在调用onTouchEvent()方法了

1. 先后关系:onTouch先于onTouchEvent执行

2. 如果onTouch返回true,表示消费,onTouchEvent就不会执行。

 

最后就是Android中的Touch事件的冲突如何解决的问题,关于事件冲突大家可以看看下面这篇博客:

https://www.jianshu.com/p/fd528c2311da

你可能感兴趣的:(自定义View)