進階必備-Android Click事件是如何觸發的?
一、背景
閱讀本篇文章前,假設你已經閱讀前一篇文章。
由於有同學問到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
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分析一下,對比於此文中的實現也不太一樣哦。
微信搜尋公眾號:南京Android部落,你想知道的知識點後臺回覆,我會拿出來一起分析探討。