Android View事件传递与源码分析

来源:互联网 发布:我的淘宝流量入口 编辑:程序博客网 时间:2024/05/24 05:18

1、MotionEvent

当手指点击屏幕后会产生一系列事件:ACTION_DOWN——手指首次触碰屏到幕ACTION_MOVE——手指在屏幕上滑动ACTION_UP——手指离开屏幕

一般情况下,手指从按下到离开屏幕会发生一系列事件:
DOWN->MOVE(MOVE事件可能有很多次,也可能没有,取决于有没有滑动和滑动距离)->UP。
Android系统这这些事件封装成MotionEvent类,在这个类里有点击事件类型、点击时间以及点击坐标等信息。
当我们手指首次按下时,会产生一个ACTION_DOWN类型的MotionEvent对象,当手指不断移动时,又会不断产生ACTION_MOVE类型的MotionEvent对象,当手指离开屏幕时,产生一个ACTION_UP类型的MotionEvent对象。这就是我们一次触摸屏幕将会生成的一系列事件,我们称为一个事件序列,以ACTION_DOWN开始,中间有数量不定的ACTION_MOVE,以ACTION_UP结束。
View分发传递的点击事件就是这些系统封装好的MotionEvent对象,并通过获取MotionEvent对象封装的信息对触摸事件进行处理。

2、事件传递

Android的UI结构如下图所示
Android UI结构
当点击事件生成,首先拿到事件的是Activity,Activity又将事件传递给PhoneWindow,PhoneWindow将事件传递给DecorView。DecorView是顶级View,我们在Activity中使用setContentView()就是将布局放在DecorView下的ContentView,而View对事件的分发也是从DecorView开始的。

1、Activity和PhoneWindow的事件传递

Activity和PhoneWindow的事件传递比较简单,通过看几行源码就明白了

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

当系统将事件传递给Activity后会调用Activity#dispatchTouchEvent(MotionEvent ev)对事件进行分发,getWindow().superDispatchTouchEvent(ev)将事件传递给PhoneWindow,并调用PhoneWindow的superDispatchTouchEvent(ev)将事件往下传递,这个传递过程层层嵌套,直到对底层的View,
如果没有任何View处理这个事件,则返回false,那么Activity的onTouchEvent()会被调用。也就是说,当最终没人处理某个事件时,Activity会处理该事件。
再看看PhoneWindow#superDispatchTouchEvent(MotionEvent event),其实它并没有做什么,单纯将事件传递非DecorView,PhoneWindow也没有处理事件的能力,即使DecorView包括它的下级View都没有处理事件,PhoneWindow也无法处理该事件,而是直接上传给Activity处理。

2、View事件传递的三个方法

前面我们介绍了Activity和PhoneWindow的事件传递,PhoneWindow将事件传递给DecorView,就开始了最重要也是最常用的事件传递过程,即事件在View的传递过程。
点击事件在View的传递由三个很重要的方法来共同完成:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent方法。

public boolean dispatchTouchEvent(MotionEvent ev)
用来进行事件的分发。如果事件能够传递给当前的View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEevent方法的影响,true表示消耗当前事件,false表示不消耗。

public boolean onInterceptTouchEvent(MotionEvent ev)
在dispatchTouchEvent方法中被调用,用来判断当前View是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。此方法只在ViewGroup中存在,View没有此方法,很好理解,作为最低层的View,已经没有下级View可以往下传递了,所以不必判断是否拦截。

public boolean onTouchEvent(MotionEvent event)
在dispatchTouchEvent中被调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接受到事件。
这三个方法的关系可以用如下伪代码表示

以下代码来自《Android开发艺术探究》public boolean dispatchTouchEvent(MotionEvent event) {boolean consume = false;if( onInterceptToucnEvent(ev) ){    consume = onTouchEvent(ev)}else{    consume = child.dispatchTouchEvent(ev)}return consume;}

在dispatchTouchEvent中调用onInterceptTouchEvent判断是否拦截事件,若拦截,则调用当前View的onTouchEvent处理事件,onTouchEvent返回的值作为dispatchTouchEvent的返回值,表示是否消耗该事件;若不拦截,则调用子View的dispatchTouchEvent,将事件往下传递,如此递归将点击事件传递下去。

3、View事件传递过程

前面介绍了负责View事件传递的三个重要方法,这里将介绍这三个方法是怎样将一个点击事件层层传递的,这里单纯从业务流程描述,不涉及三个方法的源码分析。
先看下面这张图(网上找的)
这里写图片描述
在上图中,可以把每一个虚线框视为一个dispatchTouchEvent方法。
这张图有些不完整,在确定拦截事件后,会先判断是否有设置onTouchListener,有的话onTouch()方法会优先执行,关于onTouch、onTouchEvent和onClick这三个处理事件的方法在后面会详细介绍,这里先不管。

还有就是,图中onTouchEvent部分可能会有误导,让读者以为是先返回再处理事件,事实上处理事件就在onTouchEvent中实现,然后再返回,且返回值跟是否处理事件无关,只表示是否消耗事件,这里的处理事件指的是在onTouchEvent中进行的逻辑操作。例如我们可以在onTouchEvent中根据拿到的MotionEvent处理一些事务,也可以不处理事务,返回值可以返回false,也可以返回true,但要注意的是若是返回false,那么同一事件序列的其他事件都不会传递给此View,并且交给它的父元素去处理,即调用父元素的onTouchEvent。一般情况下,我们都需要一个完整的事件序列,包括down、move及up事件,因此,若想处理一个完整的事件序列,要返回true,表示消耗此事件。关于这点,后面也会详细解释。

这张图已经基本将View事件传递的过程表现清楚,但是有一些地方还是要说明一下。
我们知道,Android中所有的控件、继承ViewGroup的各种布局都继承自View,图中,顶级父View可视为DecorView,DecorView继承自FrameLayout,父View可视为任何一个layout布局,如LinearLayout等等,子View则是我们常用的控件如Button、TextView等,内部没有onInterceptTouchEvent方法。图中的三层结构基本上可以表示出绝大多数情况了,就算嵌套再多的布局,也一样,不过是多重复几次而已。
当PhoneWindow调用DecorView的dispatchTouchEvent(MotionEvent ev),便将点击事件MotionEvent传递给了DecorView并启动DecorView的事件传递过程,而DecorView的dispatchTouchEvent又会调用子View的dispatchTouchEvent,就这样递归调用,将事件传递下去。

从图中可以看到,当一个事件一直不被拦截直至传递到最下层的View时,View的dispatchTouchEvent会调用onTouchEvent方法(先假设没有注册onTouchListener),onTouchEvent的返回值就是此View dispatchTouchEvent的返回值,若是返回false表示不消耗此事件,那么此事件就要开始往上传递,上层View会调用自己的onTouchEvent,若是上层View的onTouchEvent也返回false,又继续往上传递,重复刚才步骤。若是都没有消耗此事件,那么此事件最终会上传到Activity。

以上便是View事件传递的过程,此外还有几个结论,也是View事件传递的关键细节。
1、当某个View决定拦截事件,那么这个事件序列内的所有事件都会交给此View处理,并且它的onInterceptTouchEvent不会再被调用。

2、某个View一旦开始处理时间,如果它不消耗ACTION_DOWN事件,即onTouchEvent返回false,那么后续同一事件序列的其他事件都不会交给它处理,并且事件重新交给父元素去处理,即父元素的onTouchEvent会被调用。
这样说有些抽象,举个栗子。一个序列事件中ACTION_DOWN首先被拿到,并开始传递,传递到中间的一个View时,被拦截了,也就停止了往下传递,因为这个序列事件是这个View想要的,这个序列事件找到归宿了,接着这个View的onTouchEvent被调用(还是先不考虑onTouchListener),但是在onTouchEvent中处理完这个ACTION_DOWN事件后若是返回false,那就糟糕了,因为,这个false告诉这个View的父元素:这个不是我想要的,我包括我的下级没人想要这个事件!这表示已经往下遍历一遍所有点击区域的View了,都没人消耗这个事件,就开始往上传递了,调用父元素的onTouchEvent,就是上面流程图往上传递的部分。接着往上传递的过程中,要么有View的onTouchEvent返回true,消耗这个ACTION_DOWN事件,然后这个事件序列后面的事件都将交由消耗事件的这个View处理;要么所有View的onTouchEvent返回false,那么这个ACTION_DOWN事件将传递到Activity中,然后同一事件序列的后续事件,则在传递到DecorView后直接被作为顶级View的DecorView拦截,若DecorView不消耗该事件,那么将传递回Activity。

3、enable属性不影响View对事件的拦截消耗,将一个View设置为disable状态,该拦截消耗的还是会拦截消耗。

4、如果View不消耗ACTION_DOWN以外的其他时间,这个View仍然会收到后续同一个事件序列的事件,但因为这个View不消耗这些事件,所以这些会在这个View中消失,而不是往上传递调用父元素的onTouchEvent。

4、View对点击事件分发过程源码分析

View对点击事件的分发主要是各个继承自ViewGroup的layout来进行的,而子View已经是UI框架的最底层了,不需要也无法往下分发了。View对事件的分发就在dispatchTouchEvent中,我们接下来就分析下ViewGroup的dispatchTouchEvent方法。

顶级View(一般都是ViewGroup)拿到点击事件,并且dispatchTouchEvent被调用,开始对事件分发,这这个方法里首先判断是否拦截该事件,下面是方法中关于事件拦截的代码。

ViewGroup#dispatchTouchEvent(MotionEvent ev)部分代码if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {    //若有该View的子元素的dispatchTouchEvent返回true,则mFirstTouchTarget指向该子元素,若View将事件拦截或没有子元素消耗事件,则mFirstTouchTarget为null    //并且当新的序列事件到来时,mFirstTouchTarget会被清空    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;}

从上面的源码,我们发现onInterceptTouchEvent并不会每次都被调用,只有当事件类型为ACTION_DOWN或者mFirstTouchTarget不为空的时候才会调用,也就是说,如果当前View将ACTION_DOWN事件拦截(此时mFirstTouchTarget为null),那么当同一事件序列的ACTION_MOVE、ACTION_UP到来时,由于if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)不成立,将不会调用onInterceptTouchEvent,并且都交给该View处理。这验证了前面的第一条结论。
另外,子View可以通过requestDisallowInterceptTouchEvent方法设置FLAG_DISALLOW_INTERCEPT,使父View放弃拦截除ACTION_DOWN外的事件,之所以不能使父View放弃拦截ACTION_DOWN的原因是,当ACTION_DOWN到来时,父View会重置FLAG_DISALLOW_INTERCEPT

ViewGroup#dispatchTouchEvent(MotionEvent ev)部分代码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);//清空mFirstTouchTarget                resetTouchState();//重置各种标志            }

当View不拦截事件,事件将会下发给它的子View处理,下面这段源码是dispatchTouchEvent中关于寻找并下发事件给子View的部分:

ViewGroup#dispatchTouchEvent(MotionEvent ev)部分代码                for (int i = childrenCount - 1; i >= 0; i--) {                        final float x = ev.getX(actionIndex);                        final float y = ev.getY(actionIndex);                        // Find a child that can receive the event.                        // Scan children from front to back.                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();                        final boolean customOrder = preorderedList == null                                && isChildrenDrawingOrderEnabled();                        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;                            }                            //判断是否能够接收事件                            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);                            //如果dispatchTransformedTouchEvent返回true,说明下级子元素消耗该事件                            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();                                //对mFirstTarget赋值                                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();                    }                ······                //mFirstTouchTarget==null有两种情况                //1、没有合适的子元素可以接收事件                //2、子元素接收事件,但dispatchTouchEvent返回false            if (mFirstTouchTarget == null) {                // No touch targets so treat this as an ordinary view.                //ViewGroup自己处理事件                handled = dispatchTransformedTouchEvent(ev, canceled, null,                        TouchTarget.ALL_POINTER_IDS);            }

上面这段代码,先遍历ViewGroup的所有子元素,然后判断子元素是否能够接受到点击事件。能否接收点击事件的标准主要由两点来决定:点击事件的坐标是否在子元素的区域内和子元素是否在播放动画,只有这两点同时满足的子元素,事件才会传递给它处理,通过调用dispatchTransformedTouchEvent方法来把事件传递的符合条件的子元素的

ViewGroup#dispatchTransformedTouchEvent部分代码private boolean dispatchTransformedTouchEvent(MotionEvent event,                 boolean cancel, View child, int desiredPointerIdBits)            if (child == null) {                handled = super.dispatchTouchEvent(event);            } else {                handled = child.dispatchTouchEvent(event);            }

上面这段代码是dispatchTransformedTouchEvent的核心代码,若找到符合条件的子元素,则将子元素作为child参数传入,然后调用子元素的dispatchTouchEvent,完成一轮事件分发,若是没有找到符合,或接收事件的子元素返回false,则传入null,此时会调用super.dispatchTouchEvent(event),也就是调用所继承的父类View的dispatchTouchEvent来处理点击事件,也就是说ViewGroup自己处理了。
为什么是调用View的dispatchTouchEvent来处理呢?因为ViewGroup的dispatchTouchEvent中并没有具体处理点击事件的内容,而且在处理事件时还要判断是否有注册onTouchListener、onClickListner等事务,这些都会在View的dispatchTouchEvent处理好,所以直接调用View的dispatchTouchEvent,而且我们知道View没有onInterceptTouchEvent方法,调用View的dispatchTouchEvent会直接开始处理点击事件,而不用进行事件分发,关于View的dispatchTouchEvent会在后面介绍。
如果子元素的dispatchTouchEvent返回true,那么mFirstTouchTarget就会被赋值,指向子元素,同时跳出for循环。
mFirstTouchTarget的赋值在addTouchTarget方法中完成

ViewGroup#addTouchTargetprivate TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {        //以接收事件并返回true的子元素为参数创建TouchTarget        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);        target.next = mFirstTouchTarget;        //将创建的target赋给mFirstTouchTarget        mFirstTouchTarget = target;        return target;    }

5、View对点击事件的处理

前面我们说了,无论View还是GroupView,在最终要处理点击事件的时候,都是调用View#dispatchTouchEvent方法来处理,因为在这个方法里有关于onTouchEvent、onTouch、onClick的相关逻辑判断与调用,而且View因为已经没有子元素可以往下分发传递了,当事件传递到它时,直接就进行处理,所以它的dispatchTouchEvent都是关于处理事件的,而没有关于分发传递事件的。
还有一点,在前面我们说如果子元素不处理事件或者说不消耗事件,就交由父元素处理,显然,这里说的交由父元素处理包括了父元素的onTouch、onTouchEvent、onClick,只不过前文为了简洁,忽略onTouch和onClick

源码如下所示

View#dispatchTouchEvent    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)) {            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {                result = true;            }            //noinspection SimplifiableIfStatement            ListenerInfo li = mListenerInfo;            //判断是否有注册onTouchListner,有的话,执行onTouch            //onTouch的返回值表示是否消耗事件            //下面这个if语句,会从左往右判断,所以当前三个表达式成立时,第四个就会执行            //所以onTouch被执行,且若onTouch返回true,if成立,result为true,表示消耗事件            if (li != null && li.mOnTouchListener != null                    && (mViewFlags & ENABLED_MASK) == ENABLED                    && li.mOnTouchListener.onTouch(this, event)) {                result = true;            }            //若有执行onTouch,且返回值为false,也就是说onTouch不消耗事件            //执行OnTouchEvent,若onTouch返回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;    }

从上面的代码及注释,我们发现,onTouch的优先级比onTouchEvent高,若onTouch返回True,消耗事件,那么onTouchEvent就不能被执行。

View关于onClick的判断部分在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();        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(); //内部调用onClick方法                                }                            }                        }                        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;                    ······            }            //只要View的CLICKABLE和LONG_CLICKABLE为true,那么这个View就会消耗事件            return true;        }        return false;    }

我们从上面的代码中发现,View的onTouchEvent方法,几乎都是关于CLICK的判断。所以在自定义控件时,继承View后,得重写onTouchEvent,在这方法中根据需求,实现对事件的处理,并且,若想要控件响应click,则要在onTouchEvent 返回的时候调用View的onTouchEvent :return super.onTouchEvent(event)。
而且,当View的CLICKABLE和LONG_CLICKABLE属性,只要有一个是ENABLE(android:clickable = “true” 或android:longClickable=”true”),即使这个View是DISENABLE状态(android:enabled=”false”),那么不管是否有注册onClickListener,View都会消耗事件。

onClick的具体判断调用封装在performClick方法中

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

关于click属性,当注册监听器后,setOnClickListener和setOnLongClickListener会分别把View的click属性和long_click属性设置为true,所以想要将click或long_click设置为false,要在注册监听器之后设置。
下面是注册监听器代码

public void setOnClickListener(@Nullable OnClickListener l) {        if (!isClickable()) {            setClickable(true);        }        getListenerInfo().mOnClickListener = l;    }public void setOnLongClickListener(@Nullable OnLongClickListener l) {        if (!isLongClickable()) {            setLongClickable(true);        }        getListenerInfo().mOnLongClickListener = l;    }
原创粉丝点击