本文源码Api28

View的事件分发

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

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

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

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

Activity中事件分发处理

当我们点击界面的时候,最先调用到Activity的dispatchTouchEvent(),

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

因为我们点击事件的开始,一般是由ACTION_DOWN开始的,所以我们第一个if是要进入的,查看源码

    /**     * 

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. * *

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等键时都会触发这个方法。

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

    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。

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@Overridepublic 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的流程图

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的事件分发流程图

参考:

《Android开发艺术探索》

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


我的掘金
下面是我的公众号,欢迎大家关注我

更多相关文章

  1. android手记之----Activity
  2. Android(安卓)Annotations快速开发框架入门
  3. Android(安卓)ListView中有Button,ItemClick事件失效
  4. 【Android】如何使默认图库中获得的图像旋转成正确的方向
  5. Android项目开发实战—倒计时[Handler,Timer,TimerTask,Message]
  6. android手记之--广播接收者
  7. Android(安卓)ActionBar与ViewPager合用
  8. android Gridview生成程序快捷键的复杂方法
  9. android camera开发笔记

随机推荐

  1. android 编写简单浏览器带下载
  2. [Android(安卓)UI] ActionBar 自定义属性
  3. 自定义进度条
  4. 关于Android中的各种Dialog
  5. Android点滴记录
  6. 【Android(安卓)应用开发】GitHub 优秀的
  7. Android(安卓)APP增量升级及插件化实现方
  8. Android系统工具之Roblectric 使用过程中
  9. Android按返回键退出程序但不销毁
  10. Android运行报错:Error: Static interface