Android 触摸事件分发和处理机制解析(二)ViewGroup篇

本文未完成,持续更新中——————–
在看本片文章之前,建议花几分钟先看看我的上一篇,作为理论基础:
Android 触摸事件分发和处理机制解析(一)Activity篇

不想看那么多的话,贴上上一篇最后的结论,记住这些再往下看:

1. 触摸事件是从Activity的 dispatchTouchEvent() 开始处理的。
2. Activity的dispatchTouchEvent() 中调用了它的 onTouchEvent(),这也是它的onTouchEvent()唯一的调用 。
3. Activity的dispatchTouchEvent()又间接调用了该Activity的根ViewGroup的dispatchTouchEvent()。onTouchEvent()会不会被执行,取决于后者的返回值。如果它返回true,则Activity的onTouchEvent() 就不会执行了。

这一篇,我们就来看下ViewGroup的dispatchTouchEvent()和它的其它两个触摸事件相关方法:onInterceptTouchEvent()和onTouchEvent()。

强烈建议打开源码对照进行查看,便于熟悉代码。本文用的是Android 8.0 SDK的源码

——–如果不想看源码分析,直接看结论,可以直接跳到后面的代码总结———:.

上代码,逐步分析,这是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)) {

handled是此方法的返回值,表示是否处理。
这里的if判断是为了过滤需要分发和处理的触摸事件。以下所有我们关注的触摸事件处理都在这个if处理里。

    final int action = ev.getAction();
    final int actionMasked = action & MotionEvent.ACTION_MASK;

actionMasked表示touch事件的具体动作信息,action除了包含动作信息外,还包含了触控点信息。

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

手指按下,表示开启了一个新的触摸事件,则重置各种状态。

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

上面这一段处理事件拦截,是一个重点。

可以看到,当触摸动作为按下(ACTION_DOWN),也就是触摸事件刚开始时,程序默认都会执行onInterceptTouchEvent(ev),intercepted为执行结果。
那么intercepted的值会产生什么影响呢?这里可以先交代一下,方便下面分析。
intercepted也就是onInterceptTouchEvent(ev)的结果为true的话,则本次触摸事件就被此ViewGroup拦截了,之后传入的移动(ACTION_MOVE),抬起(ACTION_UP)事件就都不会分发给子View了,而是交给自己的onTouch()进行处理。

    // 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_DOWN进行了判断,下面会遍历各个子View,找到此触摸事件的符合条件的处理者

            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) {
                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)) {
                        ev.setTargetAccessibilityFocus(false);
                        continue;
                    }

上面两个if判断排除了不合适的子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;
                    }

如果触摸事件已经在处理了,那就退出本次遍历。
经过排除以后,以下的child,就是要接受触摸事件分发和处理的子View

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

上面一段也是个重要环节,找到合适的子View后,这里把子View传入dispatchTransformedTouchEvent()方法进行处理,这是个重要方法,它内部决定了触摸事件是否由传入的子view进行处理。
那么
1.dispatchTransformedTouchEvent()做了什么处理呢?dispatchTransformedTouchEvent()源码在本文后面部分分析,此时可以先去看看。
2.如果它返回true的话,newTouchTarget和alreadyDispatchedToNewTouchTarget都被赋了新值。这两个变量对下面有什么影响呢?带着问题继续看。

                    // 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 && childrenCount != 0) 到此结束--

            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;
            }
            上面几行处理,也是为了找到合适的触摸事件处理者
        }
    } // if (!canceled && !intercepted) 结束

注意,程序走到这里的时候,此次触摸事件可能已经是个被拦截或者取消的事件,也可能不是。

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

没找到合适的处理者。
注意此处dispatchTransformedTouchEvent方法传入的子View为null,如果已经懂了此方法的内部处理,则不难理解:传入的子View为null,表示把此ViewGroup当作一个普通View,内部最后调用了View的dispatchTouchEvent(ev)进行处理。

    } 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;
            if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                handled = true;

上面提到过的两个被赋了新值的变量,此时发挥了作用:它们表示之前的逻辑中,事件分发和处理已经完成,不用做额外处理了。

            } else {//如果暂未处理完成的话,就调用dispatchTransformedTouchEvent方法进行处理
                final boolean cancelChild = resetCancelNextUpFlag(target.child)
                        || intercepted;
                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.
    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 (onFilterTouchEventForSecurity(ev))结束

if (!handled && mInputEventConsistencyVerifier != null) {
    mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}

到这里,dispatchTouchEvent()的源码就分析完了。


看一下上面代码中调用到的一个重要方法:dispatchTransformedTouchEvent()
贴源码:

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

上面的代码总结下,其实可以简化为:

if (child == null) {
    handled = super.dispatchTouchEvent(event);
} else {
    handled = child.dispatchTouchEvent(event);
}
return handled;

如果传入的child,也就是子控件为null的话,就调用父类也就是View的dispatchTouchEvent(event)进行处理;否则就调用子控件的dispatchTouchEvent(event)进行处理。返回值也就是dispatchTouchEvent(event)的返回值。

调用子控件的dispatchTouchEvent,有两种情况,
如果是子控件是View,又分成两种情况(Button返回true,TextView返回false);
如果是ViewGroup则进入递归调用,又会执行这段代码,最终要么没有任何消耗事件的View,要么找到消费事件的View。

最后,来看一下简化版的 dispatchTouchEvent() 代码流程:
参考于https://www.cnblogs.com/linjzong/p/4191891.html

    View mTarget=null;//保存捕获Touch事件处理的View
    public boolean dispatchTouchEvent(MotionEvent ev) {

        //....其他处理,在此不管

        if(ev.getAction()==KeyEvent.ACTION_DOWN){
            //每次Down事件,都置为Null
            if(!onInterceptTouchEvent()){
            mTarget=null;

            View[] views=getChildView();
            for(int i=0;i<views.length;i++){
                if(views[i].dispatchTouchEvent(ev))
                    mTarget=views[i];
                    return true;
            }
        }

        //当子View没有捕获down事件时,ViewGroup自身处理。这里处理的Touch事件包含Down、Up和Move
        if(mTarget==null){
            return super.dispatchTouchEvent(ev);
        }

        //这一步在Action_Down中是不会执行到的,只有Move和UP才会执行到。
        return mTarget.dispatchTouchEvent(ev);

    }

这样看,就比较简单易懂了。但是我们注意到,我们并没有在源码中发现ViewGroup的onTouchEvent()的身影,它会在哪里用到呢?其实,ViewGroup类是没有重写onTouchEvent()方法的,因此如果ViewGroup要消费触摸事件,会在调用父类也就是View的dispatchTouchEvent()中,调用View的onTouchEvent()。

下一节,我们就来分析View的触摸事件分发和处理机制,也就是它的 dispatchTouchEvent() ,onTouchEvent(),以及OnTouchListener接口中的onTouch()等。
Android 触摸事件分发和处理机制解析(三)View篇

猜你喜欢

转载自blog.csdn.net/fenggering/article/details/79537633