What is the difference between onInterceptTouchEvent
and dispatchTouchEvent
in Android?
According to the android developer guide, both methods can be used to intercept a touch event (MotionEvent
), but what is the difference?
How do onInterceptTouchEvent
, dispatchTouchEvent
and onTouchEvent
interact together within a hierarchy of Views (ViewGroup
)?
The best place to demystify this is the source code. The docs are woefully inadequate about explaining this.
dispatchTouchEvent is actually defined on Activity, View and ViewGroup. Think of it as a controller which decides how to route the touch events.
For example, the simplest case is that of View.dispatchTouchEvent which will route the touch event to either OnTouchListener.onTouch if it's defined or to the extension method onTouchEvent.
For ViewGroup.dispatchTouchEvent things are way more complicated. It needs to figure out which one of its child views should get the event (by calling child.dispatchTouchEvent). This is basically a hit testing algorithm where you figure out which child view's bounding rectangle contains the touch point coordinates.
But before it can dispatch the event to the appropriate child view, the parent can spy and/or intercept the event all together. This is what onInterceptTouchEvent is there for. So it calls this method first before doing the hit testing and if the event was hijacked (by returning true from onInterceptTouchEvent) it sends a ACTION_CANCEL to the child views so they can abandon their touch event processing (from previous touch events) and from then onwards all touch events at the parent level are dispatched to onTouchListener.onTouch (if defined) or onTouchEvent(). Also in that case, onInterceptTouchEvent is never called again.
Would you even want to override [Activity|ViewGroup|View].dispatchTouchEvent? Unless you are doing some custom routing you probably should not.
The main extension methods are ViewGroup.onInterceptTouchEvent if you want to spy and/or intercept touch event at the parent level and View.onTouchListener/View.onTouchEvent for main event handling.
All in all its overly complicated design imo but android apis lean more towards flexibility than simplicity.
Because this is the first result on Google. I want to share with you a great Talk by Dave Smith on Youtube: Mastering the Android Touch System and the slides are available here. It gave me a good deep understanding about the Android Touch System:
How the Activity handles touch:
Activity.dispatchTouchEvent()
- Always first to be called
- Sends event to root view attached to Window
onTouchEvent()
- Called if no views consume the event
- Always last to be called
How the View handles touch:
View.dispatchTouchEvent()
- Sends event to listener first, if exists
View.OnTouchListener.onTouch()
- If not consumed, processes the touch itself
View.onTouchEvent()
How a ViewGroup handles touch:
ViewGroup.dispatchTouchEvent()
onInterceptTouchEvent()
- Check if it should supersede children
- Passes
ACTION_CANCEL
to active child- Return true once, consumes all subsequent events
- For each child view, in reverse order they were added
- If touch is relevant (inside view),
child.dispatchTouchEvent()
- If not handled by previous, dispatch to next view
- If no children handle event, listener gets a chance
OnTouchListener.onTouch()
- If no listener, or not handled
onTouchEvent()
- Intercepted events jump over child step
He also provides example code of custom touch on github.com/devunwired/.
Answer:
Basically the dispatchTouchEvent()
is called on every View
layer to determine if a View
is interested in an ongoing gesture. In a ViewGroup
the ViewGroup
has the ability to steal the touch events in his dispatchTouchEvent()
-method, before it would call dispatchTouchEvent()
on the children. The ViewGroup
would only stop the dispatching if the ViewGroup
onInterceptTouchEvent()
-method returns true. The difference is that dispatchTouchEvent()
is dispatching MotionEvents
and onInterceptTouchEvent
tells if it should intercept (not dispatching the MotionEvent
to children) or not (dispatching to children).
You could imagine the code of a ViewGroup doing more-or-less this (very simplified):
public boolean dispatchTouchEvent(MotionEvent ev) {
if(!onInterceptTouchEvent()){
for(View child : children){
if(child.dispatchTouchEvent(ev))
return true;
}
}
return super.dispatchTouchEvent(ev);
}
Supplemental Answer
Here are some visual supplements to the other answers. My full answer is here.
The dispatchTouchEvent()
method of a ViewGroup
uses onInterceptTouchEvent()
to choose whether it should immediately handle the touch event (with onTouchEvent()
) or continue notifying the dispatchTouchEvent()
methods of its children.
There is a lot of confusion about these methods, but it is actually not that complicated. Most of the confusion is because:
- If your
View/ViewGroup
or any of its children do not return true inonTouchEvent
,dispatchTouchEvent
andonInterceptTouchEvent
will ONLY be called forMotionEvent.ACTION_DOWN
. Without a true fromonTouchEvent
, the parent view will assume your view does not need the MotionEvents. - When none of the children of a ViewGroup return true in onTouchEvent, onInterceptTouchEvent will ONLY be called for
MotionEvent.ACTION_DOWN
, even if your ViewGroup returns true inonTouchEvent
.
Processing order is like this:
dispatchTouchEvent
is called.onInterceptTouchEvent
is called forMotionEvent.ACTION_DOWN
or when any of the children of the ViewGroup returned true inonTouchEvent
.onTouchEvent
is first called on the children of the ViewGroup and when none of the children returns true it is called on theView/ViewGroup
.
If you want to preview TouchEvents/MotionEvents
without disabling the events on your children, you must do two things:
- Override
dispatchTouchEvent
to preview the event and returnsuper.dispatchTouchEvent(ev)
; - Override
onTouchEvent
and return true, otherwise you won’t get anyMotionEvent
exceptMotionEvent.ACTION_DOWN
.
If you want to detect some gesture like a swipe event, without disabling other events on your children as long as you did not detect the gesture, you can do it like this:
- Preview the MotionEvents as described above and set a flag when you detected your gesture.
- Return true in
onInterceptTouchEvent
when your flag is set to cancel MotionEvent processing by your children. This is also a convenient place to reset your flag, because onInterceptTouchEvent won’t be called again until the nextMotionEvent.ACTION_DOWN
.
Example of overrides in a FrameLayout
(my example in is C# as I’m programming with Xamarin Android, but the logic is the same in Java):
public override bool DispatchTouchEvent(MotionEvent e)
{
// Preview the touch event to detect a swipe:
switch (e.ActionMasked)
{
case MotionEventActions.Down:
_processingSwipe = false;
_touchStartPosition = e.RawX;
break;
case MotionEventActions.Move:
if (!_processingSwipe)
{
float move = e.RawX - _touchStartPosition;
if (move >= _swipeSize)
{
_processingSwipe = true;
_cancelChildren = true;
ProcessSwipe();
}
}
break;
}
return base.DispatchTouchEvent(e);
}
public override bool OnTouchEvent(MotionEvent e)
{
// To make sure to receive touch events, tell parent we are handling them:
return true;
}
public override bool OnInterceptTouchEvent(MotionEvent e)
{
// Cancel all children when processing a swipe:
if (_cancelChildren)
{
// Reset cancel flag here, as OnInterceptTouchEvent won't be called until the next MotionEventActions.Down:
_cancelChildren = false;
return true;
}
return false;
}
I came accross very intuitive explanation at this webpage http://doandroids.com/blogs/tag/codeexample/. Taken from there:
- boolean onTouchEvent(MotionEvent ev) - called whenever a touch event with this View as target is detected
- boolean onInterceptTouchEvent(MotionEvent ev) - called whenever a touch event is detected with this ViewGroup or a child of it as target. If this function returns true, the MotionEvent will be intercepted, meaning it will be not be passed on to the child, but rather to the onTouchEvent of this View.
dispatchTouchEvent handles before onInterceptTouchEvent.
Using this simple example:
main = new LinearLayout(this){
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
System.out.println("Event - onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
//return false; //event get propagated
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
System.out.println("Event - dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
//return false; //event DONT get propagated
}
};
main.setBackgroundColor(Color.GRAY);
main.setLayoutParams(new LinearLayout.LayoutParams(320,480));
viewA = new EditText(this);
viewA.setBackgroundColor(Color.YELLOW);
viewA.setTextColor(Color.BLACK);
viewA.setTextSize(16);
viewA.setLayoutParams(new LinearLayout.LayoutParams(320,80));
main.addView(viewA);
setContentView(main);
You can see that the log willl be like:
I/System.out(25900): Event - dispatchTouchEvent
I/System.out(25900): Event - onInterceptTouchEvent
So in case you are working with these 2 handlers use dispatchTouchEvent to handle on first instance the event, which will go to onInterceptTouchEvent.
Another difference is that if dispatchTouchEvent return 'false' the event dont get propagated to the child, in this case the EditText, whereas if you return false in onInterceptTouchEvent the event still get dispatch to the EditText
You can find the answer in this video https://www.youtube.com/watch?v=SYoN-OvdZ3M&list=PLonJJ3BVjZW6CtAMbJz1XD8ELUs1KXaTD&index=19 and the next 3 videos. All the touch events are explained very well, it's very clear and full of examples.
The following code within a ViewGroup subclass would prevent it's parent containers from receiving touch events:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// Normal event dispatch to this container's children, ignore the return value
super.dispatchTouchEvent(ev);
// Always consume the event so it is not dispatched further up the chain
return true;
}
I used this with a custom overlay to prevent background views from responding to touch events.
Short answer: dispatchTouchEvent()
will be called first of all.
Short advice: should not override dispatchTouchEvent()
since it's hard to control, sometimes it can slow down your performance. IMHO, I suggest overriding onInterceptTouchEvent()
.
Because most answers mention pretty clearly about the flow touch event on activity/ view group/ view, I only add more detail about code on these methods in ViewGroup
(ignoring dispatchTouchEvent()
):
onInterceptTouchEvent()
will be called first, ACTION event will be called respectively down -> move -> up. There are 2 cases:
If you return false in 3 cases (ACTION_DOWN, ACTION_MOVE, ACTION_UP), it will consider as the parent won't need this touch event, so
onTouch()
of the parents never calls, butonTouch()
of the children will call instead; however please notice:- The
onInterceptTouchEvent()
still continue to receive touch event, as long as its children don't callrequestDisallowInterceptTouchEvent(true)
. - If there are no children receiving that event (it can happen in 2 cases: no children at the position the users touch, or there are children but it returns false at ACTION_DOWN), the parents will send that event back to
onTouch()
of the parents.
- The
Vice versa, if you return true, the parent will steal this touch event immediately, and
onInterceptTouchEvent()
will stop immediately, insteadonTouch()
of the parents will be called as well as allonTouch()
of children will receive the last action event - ACTION_CANCEL (thus, it means the parents stole touch event, and children cannot handle it from then on). The flow ofonInterceptTouchEvent()
return false is normal, but there is a little confusion with return true case, so I list it here:- Return true at ACTION_DOWN,
onTouch()
of the parents will receive ACTION_DOWN again and following actions (ACTION_MOVE, ACTION_UP). - Return true at ACTION_MOVE,
onTouch()
of the parents will receive next ACTION_MOVE (not the same ACTION_MOVE inonInterceptTouchEvent()
) and following actions (ACTION_MOVE, ACTION_UP). - Return true at ACTION_UP,
onTouch()
of the parents will NOT called at all since it's too late for the parents to steal touch event.
- Return true at ACTION_DOWN,
One more thing important is ACTION_DOWN of the event in onTouch()
will determine if the view would like to receive more action from that event or not. If the view returns true at ACTION_DOWN in onTouch()
, it means the view is willing to receive more action from that event. Otherwise, return false at ACTION_DOWN in onTouch()
will imply that the view won't receive any more action from that event.
The main difference :
•Activity.dispatchTouchEvent(MotionEvent) - This allows your Activity to intercept all touch events before they are dispatched to the window.
•ViewGroup.onInterceptTouchEvent(MotionEvent) - This allows a ViewGroup to watch events as they are dispatched to child Views.
ViewGroup's onInterceptTouchEvent()
is always the entry point for ACTION_DOWN
event which is first event to occur.
If you want ViewGroup to process this gesture, return true from onInterceptTouchEvent()
.
On returning true, ViewGroup's onTouchEvent()
will receive all subsequent events till next ACTION_UP
or ACTION_CANCEL
, and in most cases, the touch events between ACTION_DOWN
and ACTION_UP
or ACTION_CANCEL
are ACTION_MOVE
, which will normally be recognized as scrolling/fling gestures.
If you return false from onInterceptTouchEvent()
, the target view's onTouchEvent()
will be called. It will be repeated for subsequent messages till you return true from onInterceptTouchEvent()
.
Both Activity and View have method dispatchTouchEvent() and onTouchEvent.The ViewGroup have this methods too, but have another method called onInterceptTouchEvent. The return type of those methods are boolean, you can control the dispatch route through the return value.
The event dispatch in Android starts from Activity->ViewGroup->View.
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume =false;
if(onInterceptTouchEvent(ev){
consume = onTouchEvent(ev);
}else{
consume = child.dispatchTouchEvent(ev);
}
}
In my opinion dispatchTouchEvent()
is very important part on handling touches on a device. Activity
, ViewGroup
, View
have a realizations of this method. If we take into account ViewGroup
's method we will see that dispatchTouchEvent()
calls onInterceptTouchEvent()
The full SO answer is here
Small answer:
onInterceptTouchEvent comes before setOnTouchListener.
来源:https://stackoverflow.com/questions/9586032/android-difference-between-onintercepttouchevent-and-dispatchtouchevent