重学Android——View的事件分发

本文源码Api28java

View的事件分发

咱们都知道,点击事件最重要的三个方法android

//用来进行事件分发
public boolean dispatchTouchEvent(MotionEvent ev);
//判断是否拦截某个事件
public boolean onInterceptTouchEvent(MotionEvent ev);
//处理touch事件
public boolean onTouchEvent(MotionEvent event);

复制代码

他们三者的关系用伪代码表示以下:ios

public boolean dispatchTouchEvent(MotionEvent ev){
    if (onInterceptTouchEvent(ev)){
        return onTouchEvent(ev);
    }else{
        return child.dispatchTouchEvent(ev);
    }
}
复制代码

Activity中事件分发处理

当咱们点击界面的时候,最早调用到Activity的dispatchTouchEvent(),windows

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

由于咱们点击事件的开始,通常是由ACTION_DOWN开始的,因此咱们第一个if是要进入的,查看源码bash

/** * <p>All calls to your activity's {@link #onUserLeaveHint} callback will * be accompanied by calls to {@link #onUserInteraction}. This * ensures that your activity will be told of relevant user activity such * as pulling down the notification pane and touching an item there. * * <p>Note that this callback will be invoked for the touch down action * that begins a touch gesture, but may not be invoked for the touch-moved * and touch-up actions that follow. * * @see #onUserLeaveHint() */
    public void onUserInteraction() {
    }
复制代码

这个方法是个空方法,可是,咱们能够在注释中看到,这个方法主要的做用就是实现屏保功能,而且当这个Activity在栈樟树布的时候,点击Home、Back等键时都会触发这个方法。app

再来看第二个if语句,调用getWindow().superDispatchTouchEvent(ev)方法ide

public Window getWindow() {
        return mWindow;
    }
复制代码

而mWindow就是函数

final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        ...
    }
复制代码

在attach这个方法中,mWindow被赋值成了PhoneWindow,因此就至关于事件由Activity传递到了PhoneWindow中了,若是返回true,整个事件循环就结束了,返回false就意味着没人处理,全部的view的onTouchEvent都返回了false,那么Activity本身的onTouchEvent就会被调用。源码分析

PhoneWindow事件分发

@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
复制代码

能够看到phoneWindow的处理很是简单,直接把事件继续传递给了mDecor,也就是DecorView。post

DecorView

在PhoneWindow的构造函数中

public PhoneWindow(Context context, Window preservedWindow, ActivityConfigCallback activityConfigCallback) {
        this(context);
        // Only main activity windows use decor context, all the other windows depend on whatever
        // context that was given to them.
        mUseDecorContext = true;
        if (preservedWindow != null) {
            mDecor = (DecorView) preservedWindow.getDecorView();
            ...
    }
复制代码

能够看到调用getDecorView方法获取到了此DecorView

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
...
}
复制代码

这个类是一个FrameLayout,也就是一个ViewGroup,这样,事件已经由Activity传递到了Window而后再传递到了顶层的View——DecorView了。

咱们能够反过来看Activity中的setContentView

//AppCompatActivity 
public void setContentView(@LayoutRes int layoutResID) {
        this.getDelegate().setContentView(layoutResID);
}
//AppcompatDelegateImpl
@Override
public void setContentView(View v) {
    ensureSubDecor();
    ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    contentParent.addView(v);
    mOriginalWindowCallback.onContentChanged();
}
复制代码

能够看到,咱们设置进去的view,其实都是add在这个顶级的view之上。

顶级ViewGroup对点击事件的分发

在DecorView中

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }
复制代码

其实就是调用的super.dispatchTouchEvent,继续跟踪源码,到了咱们可爱的ViewGroup中了。

这个方法体代码比较多,能够分开一段一段的看,其实不少地方都有英文注释

@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)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
            //处理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;
            }
            ...
        }
        ...
    }
复制代码

从上面的代码能够看到,if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null)在这两种状况下会进行事件拦截,ACTION_DOWN就是按下的事件,mFirstTouchTarget在下面的代码中立刻要分析到,当事件由ViewGroup的子元素成功处理后,会把子元素赋值给mFirstTouchTarget。

咱们能够这么理解:当ViewGroup不拦截事件,把事件传递给子元素后,子元素处理事件,那么,mFirstTouchTarget!=null,反过来,事件由ViewGroup拦截处理了的话,mFirstTouchTarget就是null,那么当ACTION_MOVW以及ACTION_UP到来时,这个if语句就不成立了,ViewGroup就不能进入到onInterceptTouchEvent方法,而且同一序列中的其余事件都会默认交给它处理。

咱们注意到,在判断是否拦截下还有FLAG_DISALLOW_INTERCEPT这个标识位,

@Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            // We're already in this state, assume our ancestors are too
            //已是这个状态了,直接return
            return;
        }

        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass it up to our parent
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }
复制代码

能够看到,ViewGroup提供给外界用的这个方法,就是为了让final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;的boolean值进行改变的,当设置requestDisallowInterceptTouchEvent(true)时,那么dispatchTouchEvent方法中这个if语句直接进else分支,intercepted=false;ViewGroup这时不能拦截全部的除ACTION_DOWN之外的其余点击事件。

为何是除了ACTION_DOWN之外呢,其实在上面的源码分析中,咱们漏了一句,resetTouchState();,当ACTION_DOWN到来之时,VIewGroup都会调用resetTouchState,看方法名咱们也能猜到是重置touch的状态。

private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }
复制代码

在源码中,果真又把mGroupFlage的标识设为初始值。

所以,子View调用requestDisallowInterceptTouchEvent方法,并不能影响到ViewGroup对down事件的处理。

因此咱们在处理事件冲突的时候,有两种处理方法

  • 外部拦截法——父容器对事件作拦截处理,重写父容器的onInterceptTouchEvent方法
    • 须要注意的是,ACTION_DOWN事件,父容器必须返回false,由于DOWN事件是谁消费的,那么后面的MOVE和UP也只能由它来消费,事件已经不能传递给子view了
    • ACTION_MOVE就看事件由谁处理,父处理就返回true,子处理就返回false
    • ACTION_UP必须返回false,由于若是事件是交给子元素处理的,一旦父容器返回了true,那么子元素就不能点击事件了,而父容器若是是要处理的话,它的DOWN事件由本身消费,那么,之后的UP事件也一定传给了本身,在这里设了false也不影响的
  • 内部拦截法——配合requestDisallowInterceptTouchEvent这个方法来使用
    • 一样的,父容器也必须是默认只拦截了除DOWN事件之外的别的事件,若是拦截了DOWN事件,咱们设置requestDisallowInterceptTouchEvent是对它没有影响的

接下来咱们继续看这个方法体下面当ViewGroup不拦截事件时,事件会交给子view处理的源码

final View[] children = mChildren;
		//遍历子元素
        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;
            }
            //若是这个元素没法接收Pointer EVent或这个事件点压根就没有落到这个元素上
            if (!canViewReceivePointerEvents(child)
                || !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);
            //投递事件执行触摸事件
            //若是子元素仍是一个子ViewGroup,则递归调用重复过程
            //若是子元素是一个view,那么会调用view的dispatchTouchEvent,并最终由onTouchEvent来处理
            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;
            }
复制代码

上面这段代码我已经在关键地方作了注释,首先遍历ViewGroup的全部子元素,而后判断这个元素可以接收到事件:

private static boolean canViewReceivePointerEvents(@NonNull View child) {
        return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                || child.getAnimation() != null;
    }

复制代码

是否在播动画仍是点击事件的坐标不能落到子元素的区域内。若是某个子元素知足这两条件,那么这个事件就交由它来处理。

/** * 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;
        }
		...
    }
复制代码

调用dispatchTransformedTouchEvent时,源码主要就是这两句

if (child == null){
    handled = super.dispatchTouchEvent(transformedEvent);
}else{
    handled = child.dispatchTouchEvent(transformedEvent);
}
复制代码

从源码角度也证明了,当事件落到这个view的区域上,且它不是正在执行动画时,事件已经由子元素处理,当子元素的dispatchTouchEvent返回true,这时暂时不去看子元素是怎么处理的,继续把上面的代码往下读,在这个if语句中,若是子元素返回了true,会执行到

newTouchTarget = addTouchTarget(child, idBitsToAssign);
    alreadyDispatchedToNewTouchTarget = true;
    break;
复制代码

设置addTouchTarget,直接跳出循环。

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }
复制代码

能够看到,其实在addTouchTarget的内部,就把mFirstTouchTarget设置为了当前子元素。

继续往下阅读源码

if (mFirstTouchTarget == null) {
        // No touch targets so treat this as an ordinary view.
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                                                TouchTarget.ALL_POINTER_IDS);
    } 
复制代码

若是在此时mFirstTouchTarget尚未被赋值,那么只能说明,要么没有子元素处理这个事件,要么处理了而并无返回true,直接调用了dispatchTransformedTouchEvent,

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

复制代码

这个方法在上面有介绍,咱们能够看到,此时,传入的child为null,因此调用的其实就是super.dispatchTouchEvent(event);事件交由viewGroup本身来处理。

ViewGroup的流程图

ViewGroup 的事件分发流程图

View对事件的处理

当上面ViewGroup找到事件处理者,并且不是ViewGroup时,那么,就会调用到View的dispatchTouchEvent方法了

//返回结果定义在方法内部变量result中,当返回true时,表示事件已被消费
	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);
        }

        //当actionMasked为ACTION_DOWN,中止滑动事件
        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }

        //判断窗口window是否被遮挡,方法返回true,事件能够继续被分发,false再也不往下分发
        if (onFilterTouchEventForSecurity(event)) {
            //view当前是否被激活,而且有滚动事件
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            //ListenerInfo是一个内部类,定义了一些监听事件
            ListenerInfo li = mListenerInfo;
            //注意:li.mOnTouchListener就是咱们经过setOnTouchListener设置的
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            //若是咱们本身设置的setOnTouchListener,并返回了true,那么再也不处理onTouchEvent事件了!!!
            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的这个方法没有ViewGroup那么吓人,量级要少得多,我也对主要代码逐行作了注释。特别要注意的地方就是**当咱们在外面设置了setOnTouchListener时,这的优先级是比onTouchListener是要高的,若是在setOnTouchListener里返回了true,那么onTouchListener是不会处理了的。**这样的好处是方便外界来处理。

继续看事件分发到了onTouchEvent

public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

        //当前视图是否可被执行点击、长按
        //可经过java代码或者xml设置enable或clickable
        //当这些状态为false时,此clickable变量才为false,不然都是true
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        //视图是否已被销毁
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            //一个已销毁的视图,点击时依旧消费事件的,只是不能响应事件。
            return clickable;
        }
        //若是View设置有代理,还会执行TouchDelegate的onTouchEvent方法
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    //是否可点击
                    if (!clickable) {
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    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();
                                }
                                //最终ACTION_UP要执行的方法,post到UI线程中的一个runnable
                                if (!post(mPerformClick)) {
                                    performClickInternal();
                                }
                            }
                        }

                        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:
                    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                    }
                    mHasPerformedLongPress = false;

                    if (!clickable) {
                        checkForLongClick(0, x, y);
                        break;
                    }

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    //判断当前view是否在滚动容器中
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    //若是在滚动容器中,延迟返回事件,延迟时间为ViewConfiguration.getTapTimeout()
                    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, x, y);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    if (clickable) {
                        setPressed(false);
                    }
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    break;

                case MotionEvent.ACTION_MOVE:
                    if (clickable) {
                        drawableHotspotChanged(x, y);
                    }

                    // Be lenient about moving outside of buttons
                    //判断当前滑动事件是否还在当前的view中
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        // Remove any future long press/tap checks
                        //若是出view了,取消事件
                        removeTapCallback();
                        removeLongPressCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            setPressed(false);
                        }
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    }
                    break;
            }

            return true;
        }

        return false;
    }
复制代码

代码有点长,作了一些必要的注释。主要总结有几点:

  1. 当view在不可用的状态下的时候照样会消耗点击事件,只是不响应

  2. 只要view的clickable或者long_clickable有一个为true,它就会消费这个事件,返回true。

    自定义view的clickable默认返回false,但button,textview默认是true。设置setOnClickListener时也会执行clickable=true。

  3. 第一个事件必定会是DOWN事件,在滚动窗口中会有延迟响应,不在则当即响应事件

  4. ACTION_MOVE只作了处理响应事件

  5. 在ACTION_UP中,会调用performClickInternal()方法,这就是咱们熟悉的点击事件

private boolean performClickInternal() {
        // Must notify autofill manager before performing the click actions to avoid scenarios where
        // the app has a click listener that changes the state of views the autofill service might
        // be interested on.
        notifyAutofillManagerOnClick();

        return performClick();
    }

    public boolean performClick() {
        // We still need to call this method to handle the cases where performClick() was called
        // externally, instead of through performClickInternal()
        notifyAutofillManagerOnClick();

        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            
            //这里其实就是咱们熟悉的setOnClickListener.onClick()方法
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }
复制代码

View的事件分发流程图

View 的事件分发流程图

参考:

《Android开发艺术探索》

经过流程图来分析Android事件分发


个人CSDN

下面是个人公众号,欢迎你们关注我

相关文章
相关标签/搜索