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,且不执行具体逻辑,事件也不会分发到这里。
通过上面分析,我们可以得出以下结论:
分析到此结束,如有错漏,欢迎提出。