一个view内部的事件分发机制的分析总结

来源:互联网 发布:淘宝客服职责描述 编辑:程序博客网 时间:2024/06/06 12:23

这篇文章分析在一个view内部,从手指按下(ACTION_DOWN)到手指抬起(ACTION_UP)整个过程的消费过程的代码逻辑。

一、在Android中你只要触摸控件首先都会触发控件的dispatchTouchEvent方法(其实这个方法一般都没在具体的控件类中,而在他的父类View中),所以我们先来看下View的dispatchTouchEvent方法,如下:

注意:其中的OnTouch   、   OnTouchEvent   等方法的调用顺序及调用条件。

/** * Pass the touch screen motion event down to the target view, or this * view if it is the target. * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. */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);    }    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)) {        //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;        }    }    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();    }    return result;}
主要看紫色高亮部分的代码:

触摸控件后,就会调运该view的dispatchTouchEvent方法,而在dispatchTouchEvent中先执行的是onTouch方法。如果控件是ENABLE且在onTouch方法里返回了true则dispatchTouchEvent方法也返回true,不会再继续往下执行;反之,onTouch返回false则会继续向下执行onTouchEvent方法,且dispatchTouchEvent的返回值与onTouchEvent返回值相同。

当然,如果if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event))语句有一个为false则if (!result && onTouchEvent(event))就会执行,如果onTouchEvent(event)返回false则dispatchTouchEvent返回false,否则返回true。也就是说如果没有定义OnTouchListener或该view不是enabled的,或者OnTouchListener中的onTouch()方法返回false,就会执行onTouchEvent()方法。

基本结论:

  1. 触摸控件(View)首先执行dispatchTouchEvent方法。
  2. 在dispatchTouchEvent方法中先执行onTouch方法,后执行onClick方法(onClick方法在onTouchEvent中执行,下面会分析)。
  3. 如果控件(View)的onTouch返回false或者mOnTouchListener为null(控件没有设置setOnTouchListener方法)或者控件不是enable的情况下会调运onTouchEvent,dispatchTouchEvent返回值与onTouchEvent返回一样。
  4. 如果控件不是enable的设置了onTouch方法也不会执行,只能通过重写控件的onTouchEvent方法处理(上面已经处理分析了),dispatchTouchEvent返回值与onTouchEvent返回一样。
  5. 如果控件(View)是enable且onTouch返回true情况下,dispatchTouchEvent直接返回true,不会调用onTouchEvent方法。

二、好,我们再来分析一下onTouchEVent() 的源码:

/** * Implement this method to handle touch screen motion events. * <p> * If this method is used to detect click actions, it is recommended that * the actions be performed by implementing and calling * {@link #performClick()}. This will ensure consistent system behavior, * including: * <ul> * <li>obeying click sound preferences * <li>dispatching OnClickListener calls * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when * accessibility features are enabled * </ul> * * @param event The motion event. * @return True if the event was handled, false otherwise. */public boolean onTouchEvent(MotionEvent event) {    final float x = event.getX();    final float y = event.getY();    final int viewFlags = mViewFlags;    final int action = event.getAction();    if ((viewFlags & ENABLED_MASK) == DISABLED) {        if (action == 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)                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);    }    if (mTouchDelegate != null) {        if (mTouchDelegate.onTouchEvent(event)) {            return true;        }    }    if (((viewFlags & CLICKABLE) == CLICKABLE ||            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||            (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {        switch (action) {            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, 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)) {                                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:                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();                    }                    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);                }                break;            case MotionEvent.ACTION_CANCEL:                setPressed(false);                removeTapCallback();                removeLongPressCallback();                mInContextButtonPress = false;                mHasPerformedLongPress = false;                mIgnoreNextUpEvent = false;                break;            case MotionEvent.ACTION_MOVE:                drawableHotspotChanged(x, y);                // 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;        }//switch()        return true;    }//if    return false;}
还是主要看一下紫色高亮代码部分:

如果控件(View)是disenable状态,同时是可以clickable的则onTouchEvent直接消费事件返回true,反之如果控件(View)是disenable状态,同时是disclickable的则onTouchEvent直接false如果一个控件是enable且disclickable则onTouchEvent直接返回false了;反之,如果一个控件是enable且clickable则继续进入过于一个event的switch判断中,然后最终onTouchEvent都返回了true。(switch的ACTION_DOWN与ACTION_MOVE都进行了一些必要的设置与置位,)接着到手抬起来ACTION_UP时你会发现,首先判断了是否按下过,同时是不是可以得到焦点,然后尝试获取焦点,然后判断如果不是longPressed则通过post在UI Thread中执行一个PerformClick的Runnable,也就是performClick方法。

没错onClick就是在onTouchEvent中执行的,而且是在onTouchEvent的ACTION_UP事件中执行的。

总结:onTouchEvent方法中会在ACTION_UP分支中触发onClick的监听。


而且,通过实验发现。当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发下一个action。(这里是针对一次手指从down到up的操作而言的)

dispatchTouchEvent这个函数的返回值是很有意思的。它既与自己view内部对这个action的消费有关,也与是否将该action分发到其父viewgroup有关。返回true,代表view内部继续消费该action,返回false,代表分发给其父viewgroup。



希望各位多多评论,指出小弟文章中的错误及表述不当的地方!!!











0 0