Android中View的Clickable和Enabled的区别与原理

View的setClickable\setLongClickable和setEnabled方法相信大家都用过,根据方法名来理解就是设置View可不可以点击以及可不可用,但是可不可以点击以及可不可用具体表现出来又是怎样呢?他们之间又有什么区别呢?在看源码之前可能心里多多少少有点迷糊,那么,我们接下来到源码里一探究竟。阅读本篇文章需要了解基本的事件分发机制,不了解的可以先去郭霖大神的博客学习:Android事件分发机制完全解析,带你从源码的角度彻底理解(上),另外本篇源码基于Android7.0。


setClickable和setEnabled都是对mPrivateFlags做操作,判断View是否可点击以及是否可用也是基于mPrivateFlags做判断。当mPrivateFlags & CLICKABLE == CLICKABLE时代表控件是可点击的,当mViewFlags & ENABLED_MASK == ENABLED代表控件是可用的。

在 View 中对点击状态的判断主要是在 onTouchEvent 方法中,而对可用状态的判断则分布在 onTouchEvent 和 dispatchTouchEvent 中。

我们接下来就看一下View的dispatchTouchEvent方法是如何处理可点击与可用状态的。

    public boolean dispatchTouchEvent(MotionEvent event) {

        ......

        boolean result = false;

        ......

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //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;
            }
        }

        ......

        return result;
    }

上面只贴了相关的核心代码。
首先我们可以从第10行的判断语句中看出,当View是DISABLED时,将不会去执行handleScrollBarDragging(event)方法,从名字可看出该方法是处理 ScrollBar 的 drag 操作,同时第11行的result = true也不会被执行,因此代码继续往下走。而当 View 不是 DISABLED 的时候,将会执行 handleScrollBarDragging 方法,通过该方法判断 scroll bar 是否接受并消化了点击事件,如果是,则返回 true,同时执行第 11 行的 result = true。

紧接着,在第16行的判断语句中有一个条件(mViewFlags & ENABLED_MASK) == ENABLED,且紧接着在它后面执行的语句是li.mOnTouchListener.onTouch(this, event),也就是说当View不是 ENABLED 的时候将不会执行到我们通过setOnTouchListener设置进去的mOnTouchListener的onTouch方法,此时18行的result = true也不会被执行。而当 View 是 ENABLED 且对应的引用不为空的时候,将会执行 onTouch 方法,通过 onTouch 方法的返回值判断是否执行 result = true 操作。

从上面的分析可知,当View不是ENABLED的时候,第11行和第18行的result = true将会执行不到,而result的默认值是false,所以将会执行到第21行中的onTouchEvent(event),即dispatchTouchEvent的返回值result的将由onTouchEvent(event)方法的返回值决定,换句话说,事件是否交由这个View来处理将由该View的onTouchEvent(event)的返回值决定。而当 View 是 ENABLED 的时候,handleScrollBarDragging(event) 和 li.mOnTouchListener.onTouch(this, event) 都有机会执行,只要它们被执行且其中一个返回 true 时,result 将 等于 true,此时将不会执行 onTouchEvent(event)。

接下来,我们来看下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:

                    ......

                    break;

                case MotionEvent.ACTION_DOWN:

                    ......

                    break;

                case MotionEvent.ACTION_CANCEL:

                    ......

                    break;

                case MotionEvent.ACTION_MOVE:

                    ......

                    break;
            }

            return true;
        }

        return false;
    }

由第7行和第13行可知,当View是DISABLED的时候将会直接return出去,而retrun的值是由View是否可以点击来决定的。

当View是DISABLED且可点击的时候会返回 true,此时事件会一直分发到这个View中,但是实质上并没处理实质逻辑,比如onClick、onLongClick等方法不会执行。而View完全不能点击的时候将返回false,即事件不会分发到这个View中。

而当View不是DISABLED的时候代码将会执行到第17行,判断是否调用过View的setTouchDelegate 方法传入触摸代表 mTouchDelegate,如果 mTouchDelegate != null 且它的 onTouchEvent 返回 true,则退出整个方法。否则将会执行到 23 行,如果 View 可点击就会执行if块区中的代码,onClick和onLongClick等方法将会执行,并且会返回true。不可点击时则返回false,且不执行具体逻辑,事件也不会分发到这里。

通过上面分析,我们可以得出以下结论:

  1. 当View是不可用的时候,通过setOnTouchListener设置的OnTouchListener中的onTouch方法将不会执行。
  2. 当View是不可用的时候,onTouchEvent会被执行,但不会执行实质的逻辑,比如onClick、onLongClick等方法不会被执行到。此时onTouchEvent的返回值由该View能不能点击(包括长按和短按等点击状态)来决定。可以点击时返回true,否则返回false。
  3. 当View是不可点击的时候,除非调用过View的setTouchDelegate方法传入 mTouchDelegate,否则onTouchEvent必定会返回false,具体的逻辑,例如onClick、onLongClick等不会被调用。

分析到此结束,如有错漏,欢迎提出。

你可能感兴趣的:(Android中View的Clickable和Enabled的区别与原理)