Touch event dispatch

Views, layouts and RecyclerView Senior Kotlin 2.0+ API 24+ Reviewed By Best Latest Apps

Predict the output

A custom view returns false from onTouchEvent for ACTION_DOWN. What happens to the following ACTION_MOVE events?

class Knob(context: Context) : View(context) {
    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (event.actionMasked == MotionEvent.ACTION_MOVE) log("moved")
        return false
    }
}
Options
Hint 1 of 1

Who receives the rest of a gesture?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A They never reach the view; 'moved' is never logged

Correct. Not claiming ACTION_DOWN means the view isn't the target for the rest of the gesture.

Why not the others?
  • B They arrive and 'moved' is logged. Only the view that consumed DOWN gets the following events.
  • C They arrive as ACTION_CANCEL. CANCEL is sent when a parent steals a gesture you already own.
  • D The app crashes. Returning false is allowed.

30-second answer

A touch goes down from the Activity through dispatchTouchEvent on each ViewGroup. A parent can steal the gesture by returning true from onInterceptTouchEvent, and then children get ACTION_CANCEL. If not intercepted, the target child's onTouchEvent handles it; if it returns false for ACTION_DOWN, the event bubbles back up to its parents and that view gets no more events in that gesture.

Practise saying it · 35 s. No recording, just a timer

Explain touch dispatch.

Say your answer out loud, then check which of these points you covered:

  • dispatch goes down, handling bubbles up
  • onInterceptTouchEvent lets parents steal
  • Consume ACTION_DOWN to get the gesture
  • requestDisallowInterceptTouchEvent

Deep dive

Returning true for ACTION_DOWN means 'I want the rest of this gesture'.

requestDisallowInterceptTouchEvent(true) stops parents from stealing, useful for a horizontal slider in a vertical list.

GestureDetector and ViewConfiguration.getScaledTouchSlop() help tell taps from scrolls.

In Compose, pointerInput with detectors replaces most of this.

Common mistakes

  • Returning false for ACTION_DOWN and expecting MOVE events.
  • Ignoring ACTION_CANCEL and leaving a pressed state stuck.
  • Fighting a ScrollView without requestDisallowInterceptTouchEvent.

Senior take & follow-ups

Walk the down-path and up-path once, then the intercept case.

Follow-up questions

Think of your answer first, then open the hint.

  1. When does a child get ACTION_CANCEL?

    Show hint

    When a parent intercepts the gesture.

  2. How do you keep a nested slider from scrolling the list?

    Show hint

    Call requestDisallowInterceptTouchEvent(true) on the parent.

Similar on iOS: Hit-testing and the responder chain UIKit hit-tests to find the view and then uses the responder chain; Android dispatches down and bubbles up.

Suggest a fix if something here is wrong or out of date.