Without a deep understanding of how Android views handles touches, a lot of touch behaviours seem confusing. Why is this button click not working? Why is that
RecyclerView not scrolling? How do I handle nested
This article covers how touch events flow through the view hierarchy, and how a few core functions affect the flow. Part 2: Common Touch Event Scenarios shows concrete, visual examples of how these functions.
Table of Contents
- Background Knowledge
Every movement on the touch screen is reported as a
MotionEvent object. The
MotionEvent class has getters for accessing all the information associated with the event. Some commonly-used ones are:
action(the type of action being performed, more on this later)
x(x-coordinate of touch, relative to the view that handled it)
y(y-coordinate of touch, relative to the view that handled it)
rawX(absolute x-coordinate of touch, relative to the device screen)
rawY(absolute y-coordinate of touch, relative to the device screen)
eventTime(the time the event occurred, in the
As a reminder, Android screen coordinates are measured in pixels with x = 0 and y = 0 in the top left corner of the screen and the x = maxX and y = maxY in the bottom right corner.
getAction() returns an
Int constant representing different action types. The full list can be found here, but here are some of the most common ones:
ACTION_DOWN: When finger or object first comes in contact with the screen. The event contains the initial starting location of a gesture.
ACTION_UP: When finger or object lifts from the screen. Contains the final release location of a gesture.
ACTION_MOVE: Any movements in between
ACTION_UP, when the finger’s final release location is different from the initial starting location.
ACTION_CANCEL: Current gesture has been aborted. It occurs when the parent view intercepts the event from its child, for example when the user has dragged enough on a scroll view that it starts scrolling instead of letting you press the buttons inside it.
A gesture is defined as a series of
MotionEvents, starting with
ACTION_DOWN and ending with either
ACTION_CANCEL. There may be multiple
MotionEvents fired between the start and end; for example if you put your finger on the screen, make a dragging motion, then lift your finger, you’ll also trigger multiple
How Android handles MotionEvents
When a motion event occurs, it flows top-down through the view hierarchy, starting from the root of the view tree (eg.
Activity) and, if not intercepted, going all the way to the leaf view where the event happened (eg.
Button). On the way down, the views’
dispatchTouchEvent()s are called. Views can intercept the event by overriding
true, it means the event was consumed and won’t be passed onto descendent views. If it returns
false, it means the view acknowledged the event but continued passing it down.
Then after the event either reaches the leaf view or a view that intercepts and consumes it, it flows back up the chain until it’s consumed. On the way up,
onTouchEvent() is called instead. If a view’s
true, the event stops there. Any unconsumed events end at Activity’s
onTouchEvent(). So the Activity is the first to have its
onInterceptTouchEvent() called, and the last to have its
Here’s a visualization of what happens when there’s a touch on View B and none of the views handle the event:
Now let’s look at each function in detail, and how they’re implemented in
ViewGroup (subclass of
ScrollView (subclass of
This is the first function to be called when a motion event happens. It returns
true if the event was consumed by the view,
Views don’t have any children, so the
dispatchTouchEvent() implementation is simple; it calls
onTouchEvent() and any touch listeners set on the view, and returns
true if any of them return true to say that the event was consumed.
dispatchTouchEvent() does some additional state management and calls
onTouchEvent() immediately anyway, it’s recommended that custom views override
onTouchEvent() rather than
dispatchTouchEvent(), to avoid changing the default state management.
ViewGroup, it calls
false, it iterates through its child views in reverse order in which they were added. If the touch is inside the current child view, it calls
child.dispatchTouchEvent(). If the child view returns
false, signifying it didn’t consume it, the view group calls
child.dispatchTouchEvent() on the next child.
It’s recommended that custom
onInterceptTouchEvent() is intended for spying on touch events whereas
dispatchTouchEvent() does some additional state management.
ViewGroup’s; doesn’t override the function.
dispatchTouchEvent() on its children. Note that
Activity doesn’t provide an
onInterceptTouchEvent(), so overriding it in a custom activity is the only way to ensure it consumes touch events. Otherwise, if any child returns true in
onTouchEvent(), the activity’s
onTouchEvent() won’t be called.
Doesn’t have it 🤷♀️
For all intents and purposes, the default implementation returns
false. (The only exception is if the device is connected to a mouse as input and the user scrolls the mouse while focused on the
The main purpose of overriding this method is to let the
ViewGroup handle a certain type of touch event while letting the child handle another type. For example, a
ScrollView overrides it to handle scrolling while letting its child handle something like a click.
onInterceptTouchEvent(). If the event is an
ACTION_MOVE, it checks if the event has enough velocity in a supported direction to be considered a drag. If it does, the view returns
true and its children receive
ACTION_CANCELLED. The function also calls
requestDisallowInterceptTouchEvent(), meaning ancestor views’
onInterceptTouchEvent() are ignored and the scroll takes precedence over whatever the
ScrollView’s ancestors want to do on touch.
Doesn’t have it 🤷♀️
The default implementation returns
true if the view is clickable, but doesn’t do much beyond updating a few flags. The Android documentation recommends calling
super.onTouchEvent() when overriding this in a custom view, since it does some state management. The documentation also recommends overriding
performClick() instead if you only intend to handle click gestures.
View’s; doesn’t override the function.
onTouchEvent() uses the event’s information to figure out how much to scroll the view and performs the scroll. It also handles any animations associated with scrolling, for example overscroll effects.
This function also calls
The default implementation always returns
This is a function on the
ViewParent interface, to be used when a child view does not want its parent and its ancestors to intercept touch events.
ViewParent and its ancestors must obey this request for the duration of the gesture, meaning any interceptors in the view’s ancestors will be disallowed starting from
ACTION_CANCEL. You’ll have to call
requestDisallowInterceptTouchEvent() again for each new gesture.
If you don’t want to give the view’s ancestors a chance to handle the event, the view’s
onTouchEvent() must return
true so its ancestors’
onTouchEvent() don’t get triggered when the event flows back up.
ScrollView calls this inside is
onTouchEvent() if it detects a scroll.
The main resources I used for this article are:
- Mastering the Android Touch System talk
- Mastering the Android Touch System article
- The source code for MotionEvent, View, ViewGroup, ScrollView, and Activity
To see concrete examples of how these functions work, please continue to Part 2: Common Touch Event Scenarios.
Thanks to Russell and Kelvin for their valuable editing and feedback ❤️