一、背景
閱讀本篇文章前,假設你已經閱讀前一篇文章上一篇鏈接。
由於有同學問到onClick和touch事件的關係,這裏就從源碼的角度分析下onClick和onLongClick與onTouchEvent事件是怎麼關聯的。本文將通過View.java、TextView.java、Button.java的源碼作爲例子分析。
二、源碼解讀
首先我們知道View、TextView、Button三者的關係,即:Button繼承自與TextView,TextView繼承自View。
在默認我們不做任何特殊設置時,三者能響應click事件的只有Button。這是什麼原因呢?
首先我們看到View的源碼中onTouchEvent方法中:
final int viewFlags = mViewFlags;
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
在onTouchEvent方法的一開始,通過viewFlags去判斷當前View的CLICKABLE或者LONG_CLICKABLE位是否可以點擊或者長按。默認情況下,在View初始化的時候會從xml讀clickable屬性或者longclickable屬性。所以如果不在xml中設置,View和TextView是不會響應點擊事件的,那麼我們翻開Button的源碼看下爲什麼唯獨它是響應的呢?
public Button(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.buttonStyle);
}
可以看到在Button初始化的時候,設置了Button的style,我們翻開Styles.xml中找到Button:
<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>
沒錯,Button在默認的Style中就將clickable設置爲true了。所以在默認情況下Button的clickable=true。通過下面這行代碼(View.java的13743行)就可以知道,當clickable=true時,
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP){
// 處理邏輯
}
就可以進入if當中繼續處理,因爲我們響應click事件一般是在我們手按下再擡起後進行。所以,我們猜測是在MotionEvent.ACTION_UP事件後觸發click的。所以我們直接看if條件中的ACTION_UP中的邏輯:
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)) {
performClickInternal();
}
}
}
在進入處理click之前,會先判斷是否已經被長按做了處理,並且此次的ACTION_UP事件沒有被忽略掉。當這些條件都滿足後,首先會將長按的callback remove。然後會通過Post Runable的方式將PerformClick的實例post到隊列中等待處理,不直接去處理click事件而是使用post的方式是確保如果有視圖相關的更新操作完成後再觸發performClickInternal()。我們先看下PerformClick類是幹什麼的?
private final class PerformClick implements Runnable {
@Override
public void run() {
performClickInternal();
}
}
很顯然就是Runable對象,其中就是調用了performClickInternal()方法,而此方法中調用的是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);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
可以看到,最後是直接同過ListenerInfo中的mOnClickListener對象調用onClick方法。而ListenerInfo中的mOnClickListener對象就是我們通常使用view.setOnclickListener()方法設置賦值的:
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
// 賦值操作
getListenerInfo().mOnClickListener = l;
}
至此,onClick事件是如何從onTouchEvent中觸發的就可以完全看出來了。
同理,onLongClick類似,筆者這裏就不做詳細分析了。留給讀者自己去詳細的看下源碼,這裏簡單的介紹下。
onLongClick事件是如何處理的呢?因爲onCLick事件是在手指擡起後觸發的,所以我們選擇分析的是ACTION_UP事件,但是長按事件是在我們長按某個View的時候觸發的,所以並沒有將手指擡起來。所以我們肯定是在分析處理ACTION_DOWN中處理的。我們查看ACTION_DOWN事件下調用的checkForLongClick方法:
private void checkForLongClick(int delayOffset, float x, float y) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
mPendingCheckForLongPress.rememberPressedState();
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
}
我們看到onTouchEvent中的checkForLongClick中在最後一行通過postDelayed延遲發送了一個Runable對象:mPendingCheckForLongPress。延時時間是ViewConfiguration.getLongPressTimeout() - delayOffset的時間差。綜上,簡單來說,當我們按下屏幕的時候發送了一個延時的Runable,然後等到Runable被執行的時候,在通過一些標誌位判斷當前是否還滿足長按被執行的條件,如果滿足,回調listener中的onLongClick。 細節請讀者自行對着源碼看哦。
三、總結
對於一般的View來講,onTouchEvent中處理的無非是對View的一個點擊事件的處理、按下狀態的處理、長按的處理。讀者可以對類似於ScrollView這種帶滑動的控件的onTouchEvent分析一下,對比於此文中的實現也不太一樣哦。
長按下方二維碼,你想知道的知識點後臺回覆,我會拿出來一起分析探討。