Android基础三部曲『touch事件传递』

touch事件传递,这里有三个关键的方法,分别是dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent,其中View和Activity没有onInterceptTouchEvent,只有ViewGroup三个方法都有。调用过程是:dispatch(分发)->intercept(是否拦截)->ontouch(处理)。事件由Activity经ViewGroup传递到View,如果一路事件都没有被消费掉,那么事件又会由View经ViewGroup传递到Activity直到被处理掉。View的dispatchTouchEvent是将事件分发给了自己。

二、重写onInterceptTouchEvent返回true(默认false),则后续事件不会往下dispatch,子控件的diaptchTouchEvent无法接受touch事件;如果此时没有重写onTouchEvent,使之返回true,那么ACTION_DOWN将有父控件中onTouchEvent返回true的父控件处理,或者一直传递到Activity,由Activity处理;需要特别注意的是:假设A处理了ACTION_DOWN事件,则后续的ACTION_MOVE、ACTION_UP事件也由A处理!!

三、dispatchTouchEvent的调用逻辑,如果是ViewGroup,则先调用自己的onInterceptTouchEvent,如果是true,则直接返回;如果是false,则调用子View/ViewGroup的dispatchTouchEvent事件,如果子组件没有消费掉事件,则返回到本组件的onTouchEvent,则返回onTouchEvent的返回值!!

四、只重写onTouchEvent,使之返回true,表示消费了该touch事件,需要特别注意的是:假设ViewGroup A处理了ACTION_DOWN事件,不光是后续的ACTION_MOVE、ACTION_UP事件也由A处理,甚至子View的dispatchTouchEvent都不会调用,因为此时本View的dispatchTouchEvent返回true,表示事件已经被handled

五、不管是重写onTouchEvent返回true,还是重写onIterceptTouchEvent返回true,都说明事件已经不需要往子view传递了,此时子view的dispatchTouchEvent将无法收到事件。如果onIterceptTouchEvent返回true,而onTouchEvent没有重写,则事件会继续往父控件传递,直到某个父控件消费了事件,或者传递到Activity,由Activity消费掉。

六、在onTouchEvent中返回true,消费了事件,则事件不会再往上级组件传递;但是依然会调用父控件、Activity的dispatchTouchEvent事件,也就是说父控件还是有机会拦截事件的。

七、在dispatchTouchEvent中返回true(默认返回false),表示该事件已经被handled,不再继续分发;这样事件不会向传递给父控件处理,不管有没重写本组件的onTouchView消费该事件。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        ...
        // Check for interception.
        final boolean intercepted;
        //ViewGroup去判断这个事件该不该去拦截,首先是这个事件得是ACTION_DOWN事件或者该事件的mFirstTouchTarget(目标子View)是不为空的才会考虑要不要拦截。***这说明mFirstTouchTarget为空的情况下,ACTION_MOVE和ACTION_UP事件是不会经过这个拦截判断的,而是直接intercepted = true表示事件被直接拦截掉。
//      那么mFirstTouchTarget(目标子View)这个变量到底是什么呢?什么时候会为空呢?
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            //这个变量是控制是不是不允许父控件去拦截该事件的,取值是看"mGroupFlags"的取值,这里面涉及到一个方法:requestDisallowInterceptTouchEvent()方法。这个方法确定"mGroupFlags"的取值,控制请求父布局不拦截该事件,而是交给自己去做处理。这个方法在处理滑动冲突等场景时经常用到。
            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;
        }

        ...
            if (!canceled && !intercepted) {

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

                            // 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) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        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 (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }

默认流程

Touch默认流程.png

dispatchTouchEvent()中调用了dispatchTransformedTouchEvent()方法,dispatchTransformedTouchEvent()调用了super.dispatchTouchEvent()方法,所以如果重写dispatchTouchEvent方法:

  • 返回true


    dispatchTouchEvent返回true.png
  • 返回false


    dispatchTouchEvent返回false.png

onInterceptTouchEvent

  • 返回true


    onInterceptTouchEvent返回true.png
  • 返回false
    onInterceptTouchEvent和onTouchEvent如果返回false的话, 效果和默认的一样

onTouchEvent

  • 返回true


    onTouch返回true.png
  • 返回false


    onTouch返回false.png

参考:

一步步探索学习Android Touch事件分发传递机制(一)

你可能感兴趣的:(Android基础三部曲『touch事件传递』)