ViewGroup事件分发

117 阅读5分钟

1、点击事件在到达Activity后,会从dispatchTouchEvent(MotionEvent ev)方法中进行分发

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

2、getWindow()为PhoneWindow,所以随后会进入PhoneWindow中。

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

3、mDecor为DecorView,所以后面进入DecorView中的superDispatchTouchEvent(MotionEvent event)方法中。

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

4、DecorView的父类是FrameLayout,而FrameLayout中并没有重写该方法,所以会进入ViewGroup 的dispatchTouchEvent(MotionEvent ev)方法中,正式开始进入事件的分发流程。

该部分比较长也比较复杂,可分为三部分:

  • 判断该ViewGroup是否拦截事件
  • 获取ViewGroup的子View(或者是ViewGroup)列表,并进行遍历,判断是否有child会拦截事件。
  • 最后开始分发或者处理事件。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    
    ...此处删掉许多代码...
        
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // 如果收到的是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.
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        // 第一个步骤,检查次ViewGroup是否会拦截事件,如果拦截事件,intercepted赋值为true,否则为false
        final boolean intercepted;
        // ACTION_DOWN事件才会进行处理  2022/2/24
        if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
            //判断该事件是否要拦截,默认为false
            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 {
            intercepted = true;
        }

        ...此处删掉许多代码...
            
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        if (!canceled && !intercepted) {
            // 如果ViewGroup不拦截,则在其child中进行事件分发  2022/2/24
            // If the event is targeting accessibility 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事件时,才进行分发  2022/2/24
            if (actionMasked == MotionEvent.ACTION_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);

                // childrenCount表示该ViewGroup下面的子View数量  2022/2/24
                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x =
                            isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
                    final float y =
                            isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
                    // Find a child that can receive the event.
                    // Scan children from front to back.
                    // buildTouchDispatchChildList用于对这些子View进行一个排序  2022/2/24
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    // 在该循环中,将事件分发到child中进行是否拦截并处理的判断。
                    // 倒序取出,排在后面的先被分发事件  2022/2/24
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);

                        // 判断child能够接收事件  2022/2/24
                        if (!child.canReceivePointerEvents()
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }

                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            // Child is already receiving touch within its bounds.
                            // Give it the new pointer in addition to the ones it is handling.
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }

                        resetCancelNextUpFlag(child);
                        // 在dispatchTransformedTouchEvent方法中判断该Child是否拦截并处理事件  2022/2/24
                        // 如果当前child拦截事件,则返回true,如果不拦截,则返回false  2022/2/24
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // 如果该View处理该事件,则中断循环  2022/2/24
                            // 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赋值  2022/2/24
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            // 设置标识,已经进行了分发  2022/2/24
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }else{
                            // 如果当前View不处理,则继续循环。  2022/2/24
                        }

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

        // 不管在上面的循环中是否有child拦截该事件,都会走到下面的流程,然后根据根据是当前ViewGroup拦截事件还是在Child中拦截事件进行不同的处理。  2022/2/24
        
        // 分发或者处理 ,mFirstTouchTarget 默认为null,如果在上面的循环中有child拦截了事件,则会被赋值。 2022/2/24
        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
            // No touch targets so treat this as an ordinary view.
            //  进行事件处理,handled标识是否已经处理,true表示已经处理 2022/2/24
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            // 表示在上面的循环中,有child拦截了事件  2022/2/24
            // 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 {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    // 后面的ACTION_MOVE事件会通过该处进行分发  2022/2/24
                    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 (!handled && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    }
    return handled;
}
补充:

通过如下方法列出ViewGroup中的child列表,并进行一个排序,通过Z值对View进行排序

ArrayList<View> buildOrderedChildList() {
    final int childrenCount = mChildrenCount;
    if (childrenCount <= 1 || !hasChildWithZ()) return null;

    if (mPreSortedChildren == null) {
        mPreSortedChildren = new ArrayList<>(childrenCount);
    } else {
        // callers should clear, so clear shouldn't be necessary, but for safety...
        mPreSortedChildren.clear();
        mPreSortedChildren.ensureCapacity(childrenCount);
    }

    final boolean customOrder = isChildrenDrawingOrderEnabled();
    for (int i = 0; i < childrenCount; i++) {
        // add next child (in child order) to end of list
        final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
        final View nextChild = mChildren[childIndex];
        final float currentZ = nextChild.getZ();
        // insert ahead of any Views with greater Z
        int insertIndex = i;
        // Z值越大,排序越靠前。  2022/2/24
        while(insertIndex>0 && mPreSortedChildren.get(insertIndex-1).getZ() > currentZ) {
            insertIndex--;
        }
        mPreSortedChildren.add(insertIndex, nextChild);
    }
    return mPreSortedChildren;
}

判断child能够接收事件时,也有两个方法关注

方法1:根据View是否可见以及是否有动画,来判断是否可以处理事件
protected boolean canReceivePointerEvents() {
    // 判断View是否可以接收事件 -> View是否可见,如果不可见的话,是否有动画  2022/2/24
    return (mViewFlags & VISIBILITY_MASK) == VISIBLE || getAnimation() != null;
}
方法2:判断触发事件的位置,是否在此View 的绘制范围内。如果不在View范围内,不能拦截事件。
@UnsupportedAppUsage
protected boolean isTransformedTouchPointInView(float x, float y, View child,
        PointF outLocalPoint) {
    final float[] point = getTempLocationF();
    point[0] = x;
    point[1] = y;
    transformPointToViewLocal(point, child);
    final boolean isInView = child.pointInView(point[0], point[1]);
    if (isInView && outLocalPoint != null) {
        outLocalPoint.set(point[0], point[1]);
    }
    return isInView;
}

5、不管ViewGroup是否拦截事件,以及是否有child拦截事件,最后都会进入dispatchTransformedTouchEvent方法

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

    
    ...此处省略不少代码...

    // Perform any necessary transformations and dispatch.
    if (child == null) {
        // 如果child为null,直接调用View中的dispatchTouchEvent  2022/2/24
        // 如果处理事件,则返回true,如果不处理事件,则返回false  2022/2/24
        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());
        }
        // 如果child不为空,则调用传入的child 的dispatchTouchEvent方法,如果child重写了该方法,
        // 调用重写的dispatchTouchEvent方法,如果该child没有重写该方法,会调用其父类的dispatchTouchEvent方法  2022/2/24
        handled = child.dispatchTouchEvent(transformedEvent);
    }

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

进入该方法时:

  • 如果有child拦截事件,则此方法的第三个入参即为拦截事件的View(或者是ViewGroup)对象,则进入该View(或者是ViewGroup)的dispatchTouchEvent方法中继续处理,如果是ViewGroup,则可能又是以上的流程。
  • 如果没有child拦截事件,则此方法的第三个入参为null,则进入super.dispatchTouchEvent方法中进行处理,而ViewGroup 的super为View,如果可以处理,返回true,如果不处理,返回false,结束该流程。
补充:

View中的dispatchTouchEvent方法:

public boolean dispatchTouchEvent(MotionEvent event) {
    
    ...此处删除不少代码...
    
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    
    ...此处删除不少代码...

    return result;
}

可以看到,当事件分发到View中时,会首先进行mOnTouchListener事件的处理,如果没有注册OnTouchListener事件,或者在此事件中返回了false,则会继续进入onTouchEvent(event)中进行处理。

在onTouchEvent方法中,performClickInternal()方法中

public boolean onTouchEvent(MotionEvent event) {
    
    ...次数删除不少代码...

    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
            
                ...次数删除不少代码...
            
            	boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    
                    ...次数删除不少代码...

                    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) {
                            
                            ...次数删除不少代码...
                            
                            if (!post(mPerformClick)) {
                                performClickInternal();
                            }
                        }
                    }
             ...次数删除不少代码...
        }

        return true;
    }

    return false;
}

最终会进入到performClick方法中,在这里,会处理我们最熟悉的onClickListener事件。

public boolean performClick() {
    ...此处删除不少代码...
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }

    ...此处删除不少代码...

    return result;
}