The Android touch system from a slightly different perspective

Aris Papadopoulos
AndroidPub
Published in
9 min readMay 3, 2017

There are numerous resources out there (articles and videos) that explain pretty thoroughly the mechanisms of Android’s touch event system. Here’s a selection of the ones I found more helpful.

  1. Undoubtedly, the best resource available is this video presentation by Dave Smith. It goes through all main aspects of the touch system with lots of details, examples and github code.

2. This article by Benjamin Dumke-von der Ehe presents a number of tips for Android and concludes with a section on the touch system.

3. A collection of relevant articles, and a discussion on touch events, inspired by Dave Smith’s video.

4. A series of video tutorials offered by slidenerd:

https://www.youtube.com/watch?v=SYoN-OvdZ3M

Most of these resources focus on describing the flow of the algorithm that Android uses to process the touch events. The aim of this article however, is to offer insight on the touch system from a different point of view: we will try to explain why the algorithm behaves the way it does. We will start off with a user-centered approach to demonstrate that the algorithm does indeed make sense, and then we’ll dive into the details that show the flexibility that the algorithm gives to the developer.

The basic elements of the touch events processing algorithm

Dealing with touch gestures on Android can seem daunting to beginners. There are so many methods and interfaces, with names that at first don’t seem very clear or distinct from each other: dispatchTouchEvent, onInterceptTouchEvent, onTouchEvent, View.OnTouchListener

The whole touch event mechanism can be broken apart into 3 main concepts:

  1. Responding to a touch event
  2. Intercepting a touch event
  3. Dispatching the touch events

Although these actions do not happen in this particular order, it is helpful to go through them like this.

Responding to a touch event

The main mechanism for responding to a touch event is by overriding the method View.onTouchEvent in the View or ViewGroup that is meant to be the target of the event. There is also a similar method available in Activity (Activity.onTouchEvent), for processing events that happen anywhere within the activity’s content view.

The onTouchEvent approach obliges us to subclass View in order to implement the method. If we want to avoid this, we can use the View.OnTouchListener: it allows us to add touch event processing logic to a view without creating a custom View. We can attach a touch listener to the view, using the View.setOnTouchListener method.

Both the onTouchEvent and the OnTouchListener.onTouch methods return a boolean. A return value of true indicates that the event has been consumed by the method and doesn’t need to be forwarded further (we will discuss in the next paragraph what this forwarding is about). Between the onTouchEvent method and the OnTouchListener, the listener takes precedence: if there is a listener set and it returns false (or if there’s no listener at all), the onTouchEvent method will be called, but if the listener returns true, onTouchEvent will not be called.

Bubbling up the event through the view hierarchy

The whole mechanism of processing a touch event is based on passing the event around to components (View, ViewGroups and ultimately the Activity) that might be interested in the event, until one of them consumes it. It is highly important to understand the order in which the various components get an opportunity to process the event.

Looking at this from the user’s perspective, it certainly makes sense to start from the component that is “closer” to the user: this is the one that, most likely, the user wants to interact with. And by saying “close” to the user, we refer to the View that is under the user’s finger and that is a leaf of the view hierarchy tree.

The system does exactly this, it identifies the View with the characteristics mentioned above, delivers the event to that View, and gives it a chance to process it. We have already seen in the previous section, how the View can react to the event, if it wishes to do so (onTouchEvent method, or OnTouchListener).

If the View does not consume the event, Android starts walking up the view hierarchy and gives all the View’s ancestors (starting from its immediate parent) a chance to process the event, until it reaches Activity.onTouchEvent. If one of the ancestors consumes the event, the traversing stops there.

Let’s see all this with the help of an example view.

An example view that displays some contact information.

The hierarchy of the view is shown in the following component tree.

The view consists of a horizontal LinearLayout, that contains an ImageView and a vertical LinearLayout, which in turns contains 2 TextViews.

Let’s suppose that the user initiates a gesture by touching on the email TextView. The app might be configured to perform a certain action (eg create an Intent to send an email) if a specific gesture is performed over that TextView. That’s why the event is first delivered to the email TextView, so that the OnTouchListener or the onTouchEvent of the TextView can decide whether they want to process the event and how.

But it might be the case that there is no specific action to be performed on the email TextView. We might, for instance, want to treat the entire view as a whole, and process touch events in the same way, no matter if they happen above the email view, the name view of the image. A possible use case would be that by clicking anywhere in the view, we are taken to a ContactDetailsActivity of some sort.

In that case, the email TextView’s onTouchEvent would return false, and the system would starts traversing up the hierarchy tree. First it would offer the event to the vertical LinearLayout, and if its onTouchEvent method returned false, it would offer it to the horizontal LinearLayout, and that’s where we would handle the event in the above scenario.

Intercepting events

We’ve seen how the event travels along the hierarchy tree of the layout, starting from the leaves and moving towards the root, until some component decides to consume it. However, this is only part of the whole story…

As a general principle in the Android framework, a parent view has full control over its children. An example of this can be seen in the measurement step: the parent asks the children to measure themselves up, but if the measurements returned by the child view exceed the specifications defined by the parent, it’s the parent that has the final word.

Following the same principle, a parent view should be able to decide whether a touch event should be allowed to reach its children or not. That’s where the ViewGroup.onInterceptTouchEvent methods comes into play. (Note that this method only exists in ViewGroup).

Contrary to what we’ve been suggesting until now, every touch event that is received by the system is first delivered to the Activity, and not to the leaf View that lays beneath the touch point. The Activity then starts walking from the root of the view hierarchy and goes down, giving a chance to each ViewGroup that it encounters to intercept the event, before it reaches the leaf View. When it reaches the leaf View, the algorithm continues as we’ve described before.

If a ViewGroup decides to intercept the event (by returning true from its onInterceptTouchEvent method), the event will be delivered to that view’s onTouchEvent method and won’t be propagated any further down the tree. If this ViewGroups’s onTouchEvent returns false, the event will bubble up the tree as we’ve described before.

Note that a ViewGroup doesn’t have to intercept all events: it can decide to let some events through and then start intercepting. (However, from the moment that it intercepts an event, all further events of the same gesture will be delivered to it directly). Let’s see an example that illustrates it.

Suppose we have a ScrollView that contains various widgets. An event with ACTION_DOWN happens. The ScrollView should not intercept this event, as it might be, for example, the initiation of a click gesture on a button inside the ScrollView. So the ScrollView should let the event through so that the button receives it. However, if the user starts moving their finger, the ScrollView would detect that as a scroll gesture and should therefore step up , intercept the ACTION_MOVE events, and handle the scroll itself.

Dispatching events

We’ve mentioned a bit abstractly in the previous sections that the touch events are “delivered” to possible candidates for processing them, and more concisely that the events are pushed down the hierarchy tree and then they are pulled back up again. It’s time to see how all this happens.

This process is called dispatching and is handled by the methods Activity.dispatchTouchEvent, ViewGroup.dispatchTouchEvent and View.dispatchTouchEvent.

When a touch event is received, the first method that is called is Activity.dispatchTouchEvent. This method is responsible for further dispatching the event through the view hierarchy. If no component consumes the event, the method Activity.onTouchEvent is called as a last resort to process the event.

Let’s focus on how the Activity performs the dispatching. It first passes the event to the Window, which starts pushing the event down the hierarchy (starting from the DecorView, and going through all ViewGroups). For each ViewGroup that it encounters, it calls the method ViewGroup.dispatchTouchEvent.

Here’s a high-level description of what the ViewGroup’s dispatchTouchEvent method does:

a) First it calls the onInterceptTouchEvent method. If the event is intercepted, instead of forwarding the event to the ViewGroup’s children, the event is passed to the onTouchEvent method.
b) If the ViewGroup decides not to intercept the event, the ViewGroup identifies (using a hitbox algorithm) which of its children should be notified about the event, and forwards the event to them, by calling the dispatchTouchEvent method on each such child (be it a View or a ViewGroup).

The last thing missing here is to see how a View implements the dispatchTouchEvent method. This is fairly straightforward and we’ve actually already discussed it: it happens with calls to the methods OnTouchListener.onTouch and/or onTouchEvent.

Summing up

We’ve tried to explain Android’s touch event system in terms of the concepts of responding, intercepting and dispatching:

The touch events are first pushed down the hierarchy tree, where any ViewGroup is given a change to intercept them, and then they bubble up again, until some component decides to respond and consume the event. Dispatching is the mechanism that handles the propagation of the event through the hierarchy tree.

That’s not the whole picture however. We have deliberately left out some “details” of the whole system in order to keep things simple, such as the ones in the following list:

  • Touch events are actually grouped in “gestures”. A gesture starts with an ACTION_DOWN event and finishes with an ACTION_UP event. If a view does not consume the ACTION_DOWN event, it will not be notified about further events for the same gesture (for efficiency reasons): so views need to consume the ACTION_DOWN event to show that they are interested in a gesture, even if it’s not the down event that they are actually interested in.
  • There is an event type called ACTION_CANCEL that is used when a gesture starts being handled by one view and then gets transferred to another. This normally happens when interception occurs: when a ViewGroup decides to intercept an event, the event (and any further events within the same gesture) will not be dispatched to its children, but the children will receive an ACTION_CANCEL event, so that they can reset their state.
  • If a ViewGroup returns true from its onInterceptTouchEvent, this method is never called again; all further events of the gesture are directly delivered to the ViewGroup’s onTouchEvent method.
  • We’ve seen how parents are responsible for their children, and are therefore free to decide whether they want to intercept an event before it reaches the children or let it through. The framework also allows children to override this behavior by declaring that they want to prevent their parents from intercepting. This can be achieved with the View.requestDisallowTouchIntercept method.

--

--

Aris Papadopoulos
AndroidPub

Android Software Engineer. RxJava, Kotlin, SOLID, Clean code, Clean Architecture and other cool stuff.