View的事件分发机制

来源:互联网 发布:做微信推送的软件 编辑:程序博客网 时间:2024/05/16 18:34

所谓点击事件的事件分发,其实就是对MotionEvent事件的分发过程。
当一个点击操作发生时,事件最先传递给当前的activity,由activity的dispatchTouchEvent来进行事件派发,具体的工作是由activity内部的window来完成的。window会将事件传递给decorview,decorview一般就是当前界面的底层容器,可以通过activity.getWindow.getDecorView()获得。其中DecorView是继承FrameLayout的,也就是说PhoneWindow的mDecor(DecorView)为activity中所有view的根容器(ViewGroup)。
事件到达ViewGroup以后,会调用dispatchTouchEvent方法,这个方法的实现很重要:

public boolean dispatchTouchEvent(MotionEvent ev) {    if (mInputEventConsistencyVerifier != null) {        mInputEventConsistencyVerifier.onTouchEvent(ev, 1);    }    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.            // 重置事件绑定对象            cancelAndClearTouchTargets(ev);            resetTouchState();        // 处理了ACTION_DOWN的对象,也会把接下来的ACTION_MOVE和ACTION_UP都交给他处理        // Check for interception.        final boolean intercepted;        if (actionMasked == MotionEvent.ACTION_DOWN                || mFirstTouchTarget != null) {                // 可以通过修改FLAG_DISALLOW_INTERCEPT来达到事件拦截,在解决滑动冲突中可以使用该思路            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;        }        // 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;        // 若事件已被取消或被拦截,就不需要传递给子view        if (!canceled && !intercepted) {            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);                final int childrenCount = mChildrenCount;                if (newTouchTarget == null && childrenCount != 0) {                    final float x = ev.getX(actionIndex);                    final float y = ev.getY(actionIndex);                    // 传递给子view处理                    // Find a child that can receive the event.                    // Scan children from front to back.                    final View[] children = mChildren;                    final boolean customOrder = isChildrenDrawingOrderEnabled();                    for (int i = childrenCount - 1; i >= 0; i--) {                        final int childIndex = customOrder ?                                getChildDrawingOrder(childrenCount, i) : i;                        final View child = children[childIndex];                        // 判断子view是否在播放动画,以及,点击事件的坐标是否在该view的区域内                        if (!canViewReceivePointerEvents(child)                                || !isTransformedTouchPointInView(x, y, child, null)) {                            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);                        // 调用子view的dispatchTouchEvent,如果子view返回true则说明子view已拦截该事件,需要终止循环                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {                            // Child wants to receive touch within its bounds.                            mLastTouchDownTime = ev.getDownTime();                            mLastTouchDownIndex = childIndex;                            mLastTouchDownX = ev.getX();                            mLastTouchDownY = ev.getY();                            newTouchTarget = addTouchTarget(child, idBitsToAssign);                            alreadyDispatchedToNewTouchTarget = true;                            break;                        }                    }                }                                // 没有子view拦截该事件                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.        // 该系列的事件没有处理者(mFirstTouchTarget用于保存一系列的事件最开始处理的对象,如果ACTION_DOWN被处理,接下来的ACTION_MOVE,ACTION_UP都会交由此对象处理),也就是说如果子view都不处理,就ViewGroup自己处理        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;}

ViewGroup处理事件

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);        // 进行事件传递,交给子view或者父view        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()) {        // 进行事件传递,交给子view或者父view            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;}

在ViewGroup中传递都是如此的流程,直到事件被传递到view中,view的dispatchTouchEvent处理就比较简单了

public boolean dispatchTouchEvent(MotionEvent event) {    if (mInputEventConsistencyVerifier != null) {        mInputEventConsistencyVerifier.onTouchEvent(event, 0);    }    if (onFilterTouchEventForSecurity(event)) {        //noinspection SimplifiableIfStatement        ListenerInfo li = mListenerInfo;        // 若mOnTouchListener != null,且事件被onTouch拦截(return true),则该事件也就被拦截了,也就是说onTouchEvent不会被执行        if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED                && li.mOnTouchListener.onTouch(this, event)) {            return true;        }                if (onTouchEvent(event)) {            return true;        }    }        if (mInputEventConsistencyVerifier != null) {        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);    }    return false;}

在onTouchEvent中的流程

public boolean onTouchEvent(MotionEvent event) {    final int viewFlags = mViewFlags;        if ((viewFlags & ENABLED_MASK) == DISABLED) {        if (event.getAction() == 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));    }        if (mTouchDelegate != null) {        if (mTouchDelegate.onTouchEvent(event)) {            return true;        }    }        // 若view不可点击,则不处理,在此没有看到对空间enable的判断,也就是说如果view处于disabled状态也能拦截处理事件    if (((viewFlags & CLICKABLE) == CLICKABLE ||            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {        switch (event.getAction()) {            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);                   }                                       if (!mHasPerformedLongPress) {                        // 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();                            }                            // 从此看出,onClick是在ACTION_UP时调用的                            if (!post(mPerformClick)) {                                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();                }                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();                    }                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());                } else {                    // Not inside a scrolling container, so show the feedback right away                    setPressed(true);                    checkForLongClick(0);                }                break;                            case MotionEvent.ACTION_CANCEL:                setPressed(false);                removeTapCallback();                removeLongPressCallback();                break;                            case MotionEvent.ACTION_MOVE:                final int x = (int) event.getX();                final int y = (int) event.getY();                // 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;}

执行performClick,调用mOnClickListener.onClick

    public boolean performClick() {        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);        ListenerInfo li = mListenerInfo;        if (li != null && li.mOnClickListener != null) {            playSoundEffect(SoundEffectConstants.CLICK);            li.mOnClickListener.onClick(this);            return true;        }        return false;    }

下图大概的描述了事件的传递顺序,具体的处理逻辑还是得看上述源码

总结如下:

Created with Raphaël 2.1.0Activity#dispatchTouchEventPhoneWindow#superDispatchTouchEventDecorView#superDispatchTouchEventViewGroup#dispatchTouchEventViewGroup#onInterceptTouchEventViewGroup处理事件事件处理结束查找子view处理mOnTouchListener != nullmOnTouchListener.onTouchonTouchEventmOnClickListener != nullmOnClickListener.onClickyesnoyesnoyesnoyesno

其中,若在onTouch->onTouchEvent->onClick流程中,若有return true的则流程终端,也就是后面的方法不会被执行

1 0
原创粉丝点击