Mobile Development 14 min read

Why onTouch Fires Before onClick: Deep Dive into Android Touch Event Dispatch

This article explains Android's touch event dispatch mechanism, detailing MotionEvent actions, the roles of dispatchTouchEvent, onInterceptTouchEvent, and onTouchEvent, and demonstrates through code why onTouch executes before onClick and how returning true from onTouch consumes the click event.

AI Code to Success
AI Code to Success
AI Code to Success
Why onTouch Fires Before onClick: Deep Dive into Android Touch Event Dispatch

Click Event Transmission Rules

What is MotionEvent

MotionEvent represents the series of low‑level input events generated when a finger contacts the screen. The most important actions are:

ACTION_DOWN – finger just touches the screen.

ACTION_MOVE – finger moves while staying on the screen.

ACTION_UP – finger lifts off the screen.

Dispatch Process

The Android view system distributes a MotionEvent through three core methods: public boolean dispatchTouchEvent(MotionEvent ev) – entry point that decides how the event propagates. public boolean onInterceptTouchEvent(MotionEvent ev) – used by ViewGroup to optionally intercept the event before children receive it. public boolean onTouchEvent(MotionEvent ev) – final handler that processes the event for the view itself; its return value indicates whether the event was consumed.

OnClickListener is the lowest‑priority handler and is invoked only after the touch handling chain finishes.

Registering Listeners

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Log.e("TAG", "onClick executed");
    }
});
button.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        Log.e("TAG", "onTouch executed, Action=" + event.getAction());
        return false; // return true to consume the event and stop further propagation
    }
});

When the app runs, onTouch() is called before onClick(). If onTouch() returns true, the click listener is never invoked because the event is considered consumed.

Source Code Analysis

View.dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }
    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // stop nested scroll on down
        stopNestedScroll();
    }
    if (onFilterTouchEventForSecurity(event)) {
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) {
            result = true; // OnTouchListener consumed the event
        }
        if (!result && onTouchEvent(event)) {
            result = true; // View's own onTouchEvent consumed the event
        }
    }
    return result;
}

The method creates a result flag, runs a security filter, invokes any registered OnTouchListener, and finally calls onTouchEvent. If any step returns true, the event is marked as handled and propagation stops.

onFilterTouchEventForSecurity

public boolean onFilterTouchEventForSecurity(MotionEvent event) {
    if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0 && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
        // Window is obscured, drop this touch.
        return false;
    }
    return true;
}

This method blocks touch events when the view or its window is obscured, providing a security safeguard.

View.onTouchEvent

public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE ||
                                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE ||
                                 (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                // Handle click release
                if (!focusTaken) {
                    if (mPerformClick == null) {
                        mPerformClick = new PerformClick();
                    }
                    if (!post(mPerformClick)) {
                        performClickInternal();
                    }
                }
                break;
            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;
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                if (clickable) {
                    setPressed(false);
                }
                removeTapCallback();
                removeLongPressCallback();
                mInContextButtonPress = false;
                mHasPerformedLongPress = false;
                mIgnoreNextUpEvent = false;
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                break;
            case MotionEvent.ACTION_MOVE:
                if (clickable) {
                    drawableHotspotChanged(x, y);
                }
                break;
        }
        return true;
    }
    return false;
}

The method first determines whether the view is clickable (or a tooltip). It then switches on the action type to perform the appropriate logic: start long‑press detection on ACTION_DOWN, schedule a click on ACTION_UP, cancel pending callbacks on ACTION_CANCEL, and update visual feedback on ACTION_MOVE.

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);
    }
}

This runnable posts a delayed task that will invoke performLongClick if the press lasts longer than the system long‑press timeout.

performClickInternal & performClick

private boolean performClickInternal() {
    notifyAutofillManagerOnClick();
    return performClick();
}

public boolean performClick() {
    notifyAutofillManagerOnClick();
    final ListenerInfo li = mListenerInfo;
    boolean result;
    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;
}

These methods finally trigger the OnClickListener after all touch handling has completed, playing the click sound, sending accessibility events, and notifying autofill services.

Tip: The security filter checks the FILTER_TOUCHES_WHEN_OBSCURED flag. By clearing or setting this flag you can control whether touches are ignored when the window is covered by another UI element.

In summary, the event flow for a clickable view is:

dispatchTouchEvent → (OnTouchListener?.onTouch) → onTouchEvent → (if ACTION_UP) performClickInternal → performClick → OnClickListener

If onTouch() (either from an OnTouchListener or the view's own onTouchEvent) returns true, the event is considered consumed and the subsequent onClick() will not be invoked.

AndroidTouchEventViewdispatchTouchEventonClickonTouch
AI Code to Success
Written by

AI Code to Success

Focused on hardcore practical AI technologies (OpenClaw, ClaudeCode, LLMs, etc.) and HarmonyOS development. No hype—just real-world tips, pitfall chronicles, and productivity tools. Follow to transform workflows with code.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.