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