[Java] Android: Difference between onInterceptTouchEvent and dispatchTouchEvent?


Answers

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);
}
Question

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)?




Small answer:

onInterceptTouchEvent comes before setOnTouchListener.




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().

Source: http://neevek.net/posts/2013/10/13/implementing-onInterceptTouchEvent-and-onTouchEvent-for-ViewGroup.html




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




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.




Supplemental Answer

Here are some visual supplements to the other answers. My full answer is here.

The dispatchTouchEvent() method uses onInterceptTouchEvent() to choose whether it should immediately handle the touch event (with onTouchEvent()) or continue notifying the dispatchTouchEvent() methods of its children.




public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume =false;
    if(onInterceptTouchEvent(ev){
        consume = onTouchEvent(ev);
    }else{
        consume = child.dispatchTouchEvent(ev);
    }
}