directory
preface
The OnLongClickListener and OnClickListener callbacks are executed in onTouchEvent.
So let’s analyze the specific execution process:
Source code analysis
All the source code below is based on version 27. It has been abridged for ease of reading.
SetOnClickListener source
/ / the View class
public void setOnClickListener(@Nullable OnClickListener l) {
if(! isClickable()) {// Whether the click has been set, if not set
setClickable(true); } getListenerInfo().mOnClickListener = l; } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -public void setClickable(boolean clickable) {
// Set the click icon
setFlags(clickable ? CLICKABLE : 0, CLICKABLE);
}
Copy the code
SetOnLongClickListener source
/ / the View class
public void setOnLongClickListener(@Nullable OnLongClickListener l) {
if(! isLongClickable()) {// Whether the long press and click have been set, no set
setLongClickable(true); } getListenerInfo().mOnLongClickListener = l; } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -public void setLongClickable(boolean longClickable) {
// Set the long press flag
setFlags(longClickable ? LONG_CLICKABLE : 0, LONG_CLICKABLE);
}
Copy the code
First let’s look at the DOWN event handling in onTouchEvent:
/ / the View class
public boolean onTouchEvent(MotionEvent event) {
switch (action) {
case MotionEvent.ACTION_DOWN:
if(event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) { mPrivateFlags3 |= PFLAG3_FINGER_DOWN; } -------- code ① 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) {-- -- -- -- -- -- -- -- code (2) 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; }}Copy the code
When the DOWN event occurs, first set mHasPerformedLongPress to false in code ①, indicating that the long-press event has not been triggered. Then add PFLAG_PREPRESSED to mPrivateFlags and send a delay message that implements the CheckForTap interface. The delay time ViewConfiguration. GetTapTimeout fixed return 100 ().
private final class CheckForTap implements Runnable {
public float x;
public float y;
@Override
public void run(a) {
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; }... }Copy the code
After 100ms, mPrivateFlags unflags the PFLAG_PREPRESSED flag and then adds the PFLAG_PRESSED flag. Refreshes the background of pressed state. The long press event is then detected.
private void checkForLongClick(int delayOffset, float x, float y) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = newCheckForLongPress(); } mPendingCheckForLongPress.setAnchor(x, y); mPendingCheckForLongPress.rememberWindowAttachCount(); mPendingCheckForLongPress.rememberPressedState(); postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout() - delayOffset); }}Copy the code
If the long-press event is set, a delayed message is sent that implements the Runnable interface (CheckForLongPress). The delay time about ViewConfiguration. GetLongPressTimeout () – delayOffset, including ViewConfiguration. GetLongPressTimeout () fixed return 500 ms, The value of delayOffset is 100ms, and the delay duration is 400ms.
private final class CheckForLongPress implements Runnable {
@Override
public void run(a) {
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;
returnhandled; } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --public boolean performLongClick(a) {
returnperformLongClickInternal(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);
}
//------- code omitted -------
return handled;
}
Copy the code
400ms later, the run method in CheckForLongPress is executed. In the run method, the onLongClick method of mOnLongClickListener is executed. And when onLongClick returns true (performLongClickInternal returns true —-> performLongClick returns true), Set mHasPerformedLongPress to true.
So when pressed, if more than 100ms, set the View as pressed state, 500ms if the long click event is set, call back the long click listening.
Moving on to the handling of MOVE in onTouchEvent
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; }}Copy the code
Use the pointInView to determine if the finger has moved out of the View. If the View is removed, the removeTapCallback and removeLongPressCallback methods are called. And if mPrivateFlags sets PFLAG_PRESSED (pressed for more than 100ms) then setPressed(false) is used to cancel pressed and refresh the background.
private void removeTapCallback(a) {
if(mPendingCheckForTap ! =null) { mPrivateFlags &= ~PFLAG_PREPRESSED; removeCallbacks(mPendingCheckForTap); }} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --private void removeLongPressCallback(a) {
if(mPendingCheckForLongPress ! =null) { removeCallbacks(mPendingCheckForLongPress); }}Copy the code
MPrivateFlags remove PFLAG_PREPRESSED logo, remove press detection (mPendingCheckForTap) and long press (mPendingCheckForLongPress).
So when the user presses DOWN, his finger moves. If the View range is removed, the pressed state of the View will be cancelled, and the detection and long press set in the DOWN event will be removed.
Look at the handling of UP in onTouchEvent
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; } code (1) -- -- -- -- -- -- -- --booleanprepressed = (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); } code (2) -- -- -- -- -- -- -- --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) {code ③--------if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if(! post(mPerformClick)) { performClick(); }}}if (mUnsetPressedState == null) {
mUnsetPressedState = newUnsetPressedState(); } code (4) -- -- -- -- -- -- -- --if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if(! post(mUnsetPressedState)) {// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break; }}Copy the code
Check whether mPrivateFlags contains PFLAG_PRESSED or PFLAG_PREPRESSED. If it does, then code ② is executed to check whether mHasPerformedLongPress and mIgnoreNextUpEvent are false. If both are false, call removeLongPressCallback() to remove long-press detection. Go to code ③ and add a message (mPerformClick) that implements the Runnable interface. If this fails, execute the performClick() method of mPerformClick directly. Finally, the removeTapCallback method is executed, which will press detect to remove. (If the time between press and lift is less than 100ms, the press state refresh background is not performed, but the onClickListener.onclick callback is.)
Through the analysis shows that mHasPerformedLongPress to true condition is mOnLongClickListener. OnLongClick returns true. So if there is no set length according to the monitoring, or set up a long press events and mOnLongClickListener. OnLongClick returns false, Or press the length is not enough 500 ms (not mOnLongClickListener. OnLongClick trigger condition), continued to perform.
private final class PerformClick implements Runnable {
@Override
public void run(a) { performClick(); }} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --public boolean performClick(a) {
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;
}
Copy the code
You can see that monClickListener.onclick is executed in the performClick method. This is known as a clickback control.
Finally, the code continues to code ④, which sends a delayed message if prepressed is true (mPrivateFlags includes PFLAG_PREPRESSED), ViewConfiguration. GetPressedStateDuration () 64 back to a fixed value, the delay time about 64 ms.
private final class UnsetPressedState implements Runnable {
@Override
public void run(a) {
setPressed(false); }}Copy the code
Unpress state, refresh background.
So, during the UP event, if the onLongClick on the long press listener returns false (meaning mHasPerformedLongPress is not true), the onClick callback of mOnClickListener is executed. Finally, unpress the View.
Conclusion:
-
The DOWN event:
Set mHasPerformedLongPress to false. MPrivateFlags added PFLAG_PREPRESSED flag to delay sending CheckForTap for 100ms
Over 100ms: Set View to pressed state. MPrivateFlags disables the PFLAG_PREPRESSED flag and adds the PFLAG_PRESSED flag. Delay 400ms sending long press detection (CheckForLongPress)
Over 500ms: If the long click event is set, the long click listener is called back. If mOnLongClickListener onLongClick return to true, will mHasPerformedLongPress set to true.
-
MOVE events:
Once the user’s touch point is moved out of the View range, press and hold detection are removed. If the pressed state has been set (after 100ms), unpress the pressed state and refresh the background. MPrivateFlags unflags the PFLAG_PREPRESSED flag and PFLAG_PRESSED flag.
-
UP event:
Hold down time less than 100ms: Perform hold down operation –> remove long press detection –> execute mOnCLickListener. OnClick –> cancel hold down operation (delay 64ms) –> remove hold down detection
Hold down duration not exceeding 500ms: Remove long press detection –> execute mOnCLickListener. OnClick –> cancel press operation –> remove press detection
Press the length more than 500 ms: mOnLongClickListener onLongClick return true: remove long according to the test – > cancel press operation – > remove press test
MOnLongClickListener. OnLongClick returns false: remove long. According to the test – > execute mOnCLickListener onClick – > cancel press operation – > remove press test
This article is used to record the understanding and notes in the learning process. If there are any mistakes, please correct them. Thank you very much!
Event distribution series:
View event distribution (I) Distribution process
View event distribution (two) source analysis
References:
Android ViewGroup event distribution mechanism