一、背景
閱讀本篇文章前,假設你已經閱讀前一篇文章上一篇鏈接。 由於有同學問到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分析一下,對比於此文中的實現也不太一樣哦。