前两天在研究View事件分发传递时,很好奇为啥Button默认就可以点击,而TextView需要设置setClickable后才可以点击,就翻阅了下源码,写下来记录下。
通过前面《Android中view的onTouch&onClick事件分发机制详解》知道,view的触摸事件先于点击事件,且最先执行的是dispatchTouchEvent,在这个方法里会判断当前view是否可点击,然后调用onTouch、OnTouchEvent,需要注意的是这些方法只有满足条件时候才会调用,下面简单看看这个代码
frameworks/base/core/java/android/view/View.java
public boolean dispatchTouchEvent(MotionEvent event) {
...
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;
}
}
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;
}
会根据mViewFlags的值,来决定是否要把touch事件传递下去,mViewFlags是在setFlags中赋值的
frameworks/base/core/java/android/view/View.java
void setFlags(int flags, int mask) {
...
int old = mViewFlags;
mViewFlags = (mViewFlags & ~mask) | (flags & mask);
...
}
Button是TextView的子类,只是重写了父类的几个方法,如下:
frameworks/base/core/java/android/widget/Button.java
public class Button extends TextView {
public Button(Context context) {
this(context, null);
}
public Button(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.buttonStyle);
}
public Button(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public Button(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public CharSequence getAccessibilityClassName() {
return Button.class.getName();
}
}
从Button.java的实现来开,唯一不同的就是传入的defStyleRes(com.android.internal.R.attr.buttonStyle),为了验证是因为这个style导致的,我们继续看看TextView的构造方法
frameworks/base/core/java/android/widget/TextView.java
public TextView(
Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
...
/*
* Views are not normally focusable unless specified to be.
* However, TextViews that have input or movement methods *are*
* focusable by default.
*/
a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
boolean focusable = mMovement != null || getKeyListener() != null;
boolean clickable = focusable || isClickable();
boolean longClickable = focusable || isLongClickable();
n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
switch (attr) {
case com.android.internal.R.styleable.View_focusable:
focusable = a.getBoolean(attr, focusable);
break;
case com.android.internal.R.styleable.View_clickable:
clickable = a.getBoolean(attr, clickable);
break;
case com.android.internal.R.styleable.View_longClickable:
longClickable = a.getBoolean(attr, longClickable);
break;
}
}
a.recycle();
setFocusable(focusable);
setClickable(clickable);
setLongClickable(longClickable);
...
}
看到上面地方应该清楚了,是否可点击是调用了setClickable,通过上面分析知道这个方法应该会对mViewFlags赋值,来看看setClickable的定义
public void setClickable(boolean clickable) {
setFlags(clickable ? CLICKABLE : 0, CLICKABLE);
}
调用setClickable后就会对view设置可以点击的flag,这样Button可以点击就可以告一段落了,下面来看看Button里的defStyleRes,是否配置了clickable=true这个属性
frameworks/base/core/res/res/values/themes.xml
<item name="buttonStyle">@style/Widget.Button</item>
frameworks/base/core/res/res/values/styles.xml
<style name="Widget.Button">
<item name="background">@drawable/btn_default</item>
<item name="focusable">true</item>
<item name="clickable">true</item>
<item name="textAppearance">attr/textAppearanceSmallInverse</item>
<item name="textColor">@color/primary_text_light</item>
<item name="gravity">center_vertical|center_horizontal</item>
</style>
<style name="Widget.TextView">
<item name="textAppearance">?attr/textAppearanceSmall</item>
<item name="textSelectHandleLeft">?attr/textSelectHandleLeft</item>
<item name="textSelectHandleRight">?attr/textSelectHandleRight</item>
<item name="textSelectHandle">?attr/textSelectHandle</item>
<item name="textEditPasteWindowLayout">?attr/textEditPasteWindowLayout</item>
<item name="textEditNoPasteWindowLayout">?attr/textEditNoPasteWindowLayout</item>
<item name="textEditSidePasteWindowLayout">?attr/textEditSidePasteWindowLayout</item>
<item name="textEditSideNoPasteWindowLayout">?attr/textEditSideNoPasteWindowLayout</item>
<item name="textEditSuggestionItemLayout">?attr/textEditSuggestionItemLayout</item>
<item name="textEditSuggestionContainerLayout">?attr/textEditSuggestionContainerLayout</item>
<item name="textEditSuggestionHighlightStyle">?attr/textEditSuggestionHighlightStyle</item>
<item name="textCursorDrawable">?attr/textCursorDrawable</item>
<item name="breakStrategy">high_quality</item>
<item name="hyphenationFrequency">normal</item>
</style>
看到这而,要写的就差不多了,Button的style中默认配置了clickable为true,所以默认就可以点击,什么疑惑都可以从源码中找到答案,RTFSC才是真理。