View的事件分發(二)源碼分析中,咱們提到 OnLongClickListener 和 OnClickListener 的回調都是在 onTouchEvent 中執行的。java
那麼下面咱們就來分析具體的執行流程:ide
如下全部源碼都是基於版本 27。爲方便閱讀,有所刪減。源碼分析
setOnClickListener 源碼post
//View 類
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {//是否已經設置了點擊,沒有就設置
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
-----------------------------------------------------------------------
public void setClickable(boolean clickable) {
//設置點擊標識
setFlags(clickable ? CLICKABLE : 0, CLICKABLE);
}
複製代碼
setOnLongClickListener 源碼學習
//View 類
public void setOnLongClickListener(@Nullable OnLongClickListener l) {
if (!isLongClickable()) {//是否已經設置了長按點擊,沒有就設置
setLongClickable(true);
}
getListenerInfo().mOnLongClickListener = l;
}
-----------------------------------------------------------------------
public void setLongClickable(boolean longClickable) {
//設置長按標識
setFlags(longClickable ? LONG_CLICKABLE : 0, LONG_CLICKABLE);
}
複製代碼
//View 類
public boolean onTouchEvent(MotionEvent event) {
switch (action) {
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
--------代碼①
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
if (isInScrollingContainer) {
--------代碼②
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
}
}
複製代碼
在 DOWN 事件來臨的時候,首先在 代碼① 處將 mHasPerformedLongPress 賦值爲 false,表示長按事件還未觸發。而後在 代碼② 處爲 mPrivateFlags 添加 PFLAG_PREPRESSED 標識,又發送了一個實現了 Runnable 接口(CheckForTap)的延遲消息,延遲時長 ViewConfiguration.getTapTimeout() 固定返回 100。this
private final class CheckForTap implements Runnable {
public float x;
public float y;
@Override
public void run() {
mPrivateFlags &= ~PFLAG_PREPRESSED;
setPressed(true, x, y);
checkForLongClick(ViewConfiguration.getTapTimeout(), x, y);
}
}
------------------------------------------------------------------
public void setPressed(boolean pressed) {
......
if (pressed) {
mPrivateFlags |= PFLAG_PRESSED;
} else {
mPrivateFlags &= ~PFLAG_PRESSED;
}
......
}
複製代碼
100ms 後,將 mPrivateFlags 取消 PFLAG_PREPRESSED 標識,而後添加了 PFLAG_PRESSED 標識。刷新按下狀態的背景。而後檢測長按事件。spa
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);
}
}
複製代碼
若是設置了長按事件,就發送一個實現了 Runnable 接口(CheckForLongPress)的延遲消息。延時時長爲 ViewConfiguration.getLongPressTimeout() - delayOffset
,其中 ViewConfiguration.getLongPressTimeout() 固定返回 500ms,delayOffset 爲 100ms,延遲時長爲 400ms。.net
private final class CheckForLongPress implements Runnable {
@Override
public void run() {
if ((mOriginalPressedState == isPressed()) && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
if (performLongClick(mX, mY)) {
mHasPerformedLongPress = true;
}
}
}
}
---------------------------------------------------------------
public boolean performLongClick(float x, float y) {
mLongClickX = x;
mLongClickY = y;
final boolean handled = performLongClick();
mLongClickX = Float.NaN;
mLongClickY = Float.NaN;
return handled;
}
---------------------------------------------------------------
public boolean performLongClick() {
return performLongClickInternal(mLongClickX, mLongClickY);
}
---------------------------------------------------------------
private boolean performLongClickInternal(float x, float y) {
boolean handled = false;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLongClickListener != null) {
handled = li.mOnLongClickListener.onLongClick(View.this);
}
//-------代碼省略-------
return handled;
}
複製代碼
400ms後,執行了 CheckForLongPress 中的 run 方法。run 方法中,執行了 mOnLongClickListener 的 onLongClick 方法。而且 onLongClick 返回爲 true 時( performLongClickInternal 返回爲 true ----> performLongClick 返回 true ),將 mHasPerformedLongPress 設置爲 true。code
因此在按下的時候,若是超過100ms,就設置 View 爲按下狀態,500ms後若是設置了長按點擊事件,就回調長按點擊的監聽。orm
public boolean onTouchEvent(MotionEvent event) {
switch (action) {
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
}
複製代碼
經過 pointInView 來判斷手指是否移出了這個 View 。若是移出了這個 View,就調用 removeTapCallback 和 removeLongPressCallback 方法。而且若是 mPrivateFlags 設置了 PFLAG_PRESSED 標識(按下時長超過 100ms)就執行 setPressed(false),取消按下狀態,刷新背景。
private void removeTapCallback() {
if (mPendingCheckForTap != null) {
mPrivateFlags &= ~PFLAG_PREPRESSED;
removeCallbacks(mPendingCheckForTap);
}
}
---------------------------------------------------------------
private void removeLongPressCallback() {
if (mPendingCheckForLongPress != null) {
removeCallbacks(mPendingCheckForLongPress);
}
}
複製代碼
mPrivateFlags 移除 PFLAG_PREPRESSED 標識,移除按下檢測(mPendingCheckForTap)和長按檢測(mPendingCheckForLongPress)。
因此用戶在按下後,手指移動,若是移除了 View 的範圍,就取消 View 的按下狀態,移除了 DOWN 事件中設置的 檢測 和 長按。
public boolean onTouchEvent(MotionEvent event) {
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
代碼①--------
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
setPressed(true, x, y);
}
代碼②--------
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) {
代碼③--------
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
代碼④--------
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
}
}
複製代碼
首先在 代碼① 判斷 mPrivateFlags 是否包含了 PFLAG_PRESSED 或 PFLAG_PREPRESSED。若是包含,就執行到了 代碼② ,判斷 mHasPerformedLongPress 和 mIgnoreNextUpEvent 是否爲 false。都爲 false 就調用 removeLongPressCallback() 方法,移除長按檢測。執行到 代碼③,添加一個實現 Runnable 接口的消息(mPerformClick),若是添加失敗,則直接執行 mPerformClick 的 performClick() 方法。最後執行 removeTapCallback 方法,將按下檢測移除。(若是從按下到擡起時間間隔不到100ms,不會執行按下狀態刷新背景,可是會執行 onClickListener.onClick 回調。)
經過前面的分析可知,mHasPerformedLongPress 爲 true 的條件就是 mOnLongClickListener.onLongClick 返回 true。因此若是沒有設置長按監聽,或者設置了長按事件且 mOnLongClickListener.onLongClick 返回 false,或者按下時長不夠 500ms(沒有達到 mOnLongClickListener.onLongClick 觸發條件),則繼續往下執行。
private final class PerformClick implements Runnable {
@Override
public void run() {
performClick();
}
}
---------------------------------------------------------------
public boolean performClick() {
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;
}
複製代碼
能夠看到 performClick 方法中執行了 mOnClickListener.onClick。也就是咱們平時熟知的控件點擊回調。
最後,代碼繼續執行到 代碼④,若是 prepressed 爲 true(mPrivateFlags 包含了 PFLAG_PREPRESSED),則發送一個延遲消息,ViewConfiguration.getPressedStateDuration() 發回固定值 64,延遲時長爲 64ms。
private final class UnsetPressedState implements Runnable {
@Override
public void run() {
setPressed(false);
}
}
複製代碼
取消了按下狀態,刷新背景。
因此,在 UP 事件的時候,若是長按監聽的 onLongClick 返回 false(意味着 mHasPerformedLongPress 不爲 true),就執行 mOnClickListener 的 onClick 回調。最後再取消 View 的按下狀態。
DOWN 事件:
將 mHasPerformedLongPress 設置爲 false。mPrivateFlags 添加 PFLAG_PREPRESSED 標識,延遲 100ms 發送按下檢測(CheckForTap)
超過 100ms : 就設置 View 爲按下狀態。mPrivateFlags 取消 PFLAG_PREPRESSED 標識,添加 PFLAG_PRESSED 標識。延遲 400ms 發送長按檢測(CheckForLongPress)
超過 500ms : 若是設置了長按點擊事件,就回調長按點擊的監聽。若是 mOnLongClickListener.onLongClick 返回爲 true,就將 mHasPerformedLongPress 設置爲 true。
MOVE 事件:
用戶的觸摸點移出了 View 的範圍後,就將 按下檢測和 長按檢測移除。若是已經設置了按下狀態(已通過了 100ms) ,就取消按下狀態,刷新背景。mPrivateFlags 取消 PFLAG_PREPRESSED 標識和 PFLAG_PRESSED 標識。
UP 事件:
按下時長不超過100ms:執行按下操做---> 移除長按檢測 --->執行 mOnCLickListener.onClick ---> 取消按下操做(延遲 64ms)--->移除按下檢測
按下時長不超過500ms:移除長按檢測 ---> 執行 mOnCLickListener.onClick ---> 取消按下操做--->移除按下檢測
按下時長超過500ms:mOnLongClickListener.onLongClick 返回 true:移除長按檢測 ---> 取消按下操做--->移除按下檢測
mOnLongClickListener.onLongClick 返回 false:移除長按檢測 ---> 執行 mOnCLickListener.onClick ---> 取消按下操做--->移除按下檢測
本篇文章用於記錄學習過程當中的理解和筆記,若有錯誤,請批評指正,萬分感謝!