I have extended a FrameLayout and overriden the onInterceptTouchEvent() and onTouchEvent(). This custom Framelayout will be the root layout of my ListView rows.
My requirement is that, if I tap on a row in the ListView, the onItemClickListener should be called, if I scroll (up/down) the list scrolls as usual. However if I scroll (left/right) or swipe (left/right), the custom FrameLayout should take some action based upon which row I perform the action on.
My idea was that, I would override onInterceptTouchEvent, monitor the events and if I see that ACTION_MOVE has been called enough to make it a scroll/swipe then I take control and do the task. If it's a tap or some other gesture then let the default flow occur.
The problem I face now is that if my framelayout does not contain any touchable child, then I receive no calls post ACTION_DOWN. And if I receive the ACTION_DOWN in my onTouchEvent() so as to analyze the kind of gesture, then I have no control left to let the default flow occur if I calculate and find that the action was actually just a tap/vertical scroll.
Is there some way in which a child can get hold of the events (ACTION_DOWN and ACTION_MOVE) and if a condition satisfies then perform an action or else let the parent handle these same events?
Is there some other approach that I can take to satisfy this requirement?
I am having a gesture listener attached to a view and I have onSingleTap Event handled.
It handles it properly if I tap anywhere in the view. But say if the View is having any subview and if I am tapping the subview the event does not get triggered.
Is there a way to pass this touch from child to parent? And also the children contains BUTTONS. so if the press is on a child button it should not pass the touch to parent. Otherwise it must pass it to parent. Anyway to achieve this?
You can override the Activity's dispatchTouchEvent(MotionEvent) in order to handle the dispatching yourself.
A good handling would be to first check if it triggers the gesture in your view and if not just call super.dispatchTouchEvent(MotionEvent);
The one thing you have to care about while doing this is to keep the gestures coherent with the rest of the platform.
I am intending to transfer a touch event from a parent view to its child. I tried some constructions but so far I have not succeeded to assign touch handling to another view, but only delegating touch handling from parent to child views, which is not quite the same.
This is what I like to achieve:
The green dot is a Touch Down Event on the parent, then I am dragging towards another view, which is than picked up on the blue spot (firing Touch-Down on the child view) and from this point the parent view is not involved anymore, like I would have touched the child in the first place.
I could delegate the touch event by extending the parents onTouchEvent method, and then calling child.dispatchTouchEvent(), when entering the childs view-bounds, but I would like to avoid the parents involvement.
From the View.OnTouchListener documentation, the return of an OnTouch() is:
Returns
True if the listener has consumed the event, false otherwise.
Basically, if you want the parent to react to the onTouchListener, return false. If you want it to not react, return true.
I wasn't able to find a good way to intercept an ACTION_UP on a view other than the one ACTION_DOWN was used on.
This is intended for people with disabilities where the initial point where the screen is touched is not their intended target.
Tried to implement onTouchEvent for the whole layout and intercept action_up (that wasn't consumed) from all children like the documentation suggested but it did not work as expected. Ended up setting OnTouchListeners for every button and funneling them to the layout onTouchEvent manually in order to have both children view action_up and the layout action_up events.
I'm not asking how to handle touch events, but what is going on behind the scenes? If there are several nested widgets, what order do they see the events in? Does the developer have any control over it? Ideally I would like a document on the subject.
Let's take a look at a visual example.
When a touch event occurs, first everyone is notified of the event, starting at the Activity and going all the way to the view on top. Then everyone is given a chance to handle the event, starting with the view on top (view having highest Z order in the touch region) and going all the way back to the Activity. So the Activity is the first to hear of it and the last to be given a chance to handle it.
If some ViewGroup wants to handle the touch event right away (and not give anyone else down the line a chance at it) then it can just return true in its onInterceptTouchEvent(). An Activity doesn't have onInterceptTouchEvent() but you can override dispatchTouchEvent() to do the same thing.
If a View (or a ViewGroup) has an OnTouchListener, then the touch event is handled by OnTouchListener.onTouch(). Otherwise it is handled by onTouchEvent(). If onTouchEvent() returns true for any touch event, then the handling stops there. No one else down the line gets a chance at it.
More detailed explanation
The above diagram makes things a little more simple than they actually are. For example, between the Activity and ViewGroup A (the root layout) there is also the Window and the DecorView. I left them out above because we generally don't have to interact with them. However, I will include them below. The description below follows a touch event through the source code. You can click a link to see the actual source code.
(Update: the source code has been updated so the line numbers are off now, but clicking the links will still get you to the right file. Just do a search for the method name.)
The Activity's dispatchTouchEvent() is notified of a touch event. The touch event is passed in as a MotionEvent, which contains the x,y coordinates, time, type of event, and other information.
The touch event is sent to the Window's superDispatchTouchEvent(). Window is an abstract class. The actual implementation is PhoneWindow.
The next in line to get the notification is DecorView's superDispatchTouchEvent(). DecorView is what handles the status bar, navigation bar, content area, etc. It is actually just a FrameLayout subclass, which is itself a subclass of ViewGroup.
The next one to get the notification (correct me if I'm wrong) is the content view of your activity. That is what you set as the root layout of your activity in xml when you create the layout in the Android Studio's Layout Editor. So whether you choose a RelativeLayout, a LinearLayout, or a ConstraintLayout, they are all subclasses of ViewGroup. And ViewGroup gets notified of the touch event in dispatchTouchEvent(). This is the ViewGroup A in my diagrams above.
The ViewGroup will notify any children it has of the touch event, including any ViewGroup children. This is ViewGroup B in my diagrams above.
Anywhere along the way, a ViewGroup can short-circuit the notification process by returning true for onInterceptTouchEvent().
Assuming no ViewGroup cut the notifications short, the natural end of the line for the notifications is when the View's dispatchTouchEvent() get's called.
Now it is time, to start handling the events. If there is an OnTouchListener, then it gets the first chance at handling the touch event with onTouch(). Otherwise, the View's onTouchEvent() gets to handle it.
Now all the ViewGroups recursively up the line get a chance to handle the touch event in the same way that View did. Although, I didn't indicate this in the diagram above, a ViewGroup is a View subclass, so everything I described about OnTouchListener.onTouch() and onTouchEvent() also applies to ViewGroups.
Finally, if no one else wanted it, the Activity also gets the last chance to handle the event with onTouchEvent().
FAQ
When would I ever need to override dispatchTouchEvent()?
Override it in the Activity if you want to catch a touch event before any of the views get a chance at it. For a ViewGroup (including the root view), then just override onInterceptTouchEvent() and onTouchEvent().
When would I ever need to override onInterceptTouchEvent()?
If you just want to spy of the touch notifications that are coming in, you can do it here and return false.
However, 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 does this to handle scrolling while letting its child handle something like a Button click. Conversely, if the child view doesn't want to let its parent steal its touch event, it can call requestDisallowTouchIntercept().
What are the touch event types?
The main ones are
ACTION_DOWN - This is the start of a touch event. You should always return true for the ACTION_DOWN event in onTouchEvent if you want to handle a touch event. Otherwise, you won't get any more events delivered to you.
ACTION_MOVE - This event is continuously fired as you move your finger across the screen.
ACTION_UP - This is the last event of a touch event.
A runner up is ACTION_CANCEL. This gets called if a ViewGroup up the tree decides to intercept the touch event.
You can view the other kinds of MotionEvents here. Since Android is multi-touch, events are also fired when other fingers ("pointers") touch the screen.
Further study
Android onTouchEvent Part 1, Part 2, and Part 3 (YouTube video - good summary of some of the links below)
Mastering the Android Touch System (thorough video by Google developer)
Android UI Internal : Pipeline of View's Touch Event Handling
Managing Touch Events in a ViewGroup (Android docs)
Input Events (Android docs)
Gestures and Touch Events
From Activity viewpoint:
Touch events are delivered first to Activity.dispatchTouchEvent. It's where you may catch them first.
Here they get dispatched to Window, where they traverse View hierarchy, in such order that Widgets that are drawn last (on top of other widgets) have chance to process touch in View.onTouchEvent first. If some View returns true in onTouchEvent, then traversal stops and other Views don't receive touch event.
Finally, if no View consumes touch, it's delivered to Activity.onTouchEvent.
That's all your control. And it's logical that what you see drawn on top of something else, has chance to process touch event before something drawn below it.
Android Touch event
I have prepared a high level diagram that should illustrate a simple flow.
dispatchTouchEvent() - Activity, ViewGroup, View
onInterceptTouchEvent() - ViewGroup
onTouch() - ViewGroup, View. Using setOnTouchListener()
onTouchEvent() - Activity, ViewGroup, View
[iOS onTouch]
following Suragch's answer,
pseudocode:
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if (onInterceptTouchEvent(ev) {
consume = onTouchEvent(ev);
} else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
ref:Android开发艺术探索