Android View Event Distribution Mechanism

Keywords: Android REST

Reprint: Android View Event Distribution Mechanism

Surely many android developers have encountered gesture conflicts, and we usually solve these problems through internal and external interception methods. To understand the principle, you must understand the distribution mechanism of View. Before that, let's look at the following three very important ways:
dispatchTouchEvent()
onInterceptTouchEvent()
onTouchEvent()
Let's look at these three methods separately:

  • dispatchTouchEvent()

    This method is used to deal with event distribution. If the event can be passed to the current View, this method must be invoked. The source code of the method in View:

   /**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }

        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }

        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }

        // Clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
        // of the gesture.
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        return result;
    }

Simplified as follows:

public boolean dispatchTouchEvent(Motion e){
     boolean result=false;
     if(onInterceptTouchEvent(e)){
     //If the current View intercepts an event, the event is handled by the current View, which calls onTouchEvent()
        result=onTouchEvent(e);
     }else{
        //If not intercepted, send it to its child View for distribution
        result=child.dispatchTouchEvent(e);
     }
     return result;
}

From the above, we can see the relationship between the three methods.

  • The onInterceptTouchEvent() method can be seen above that it is called in dispatchTouchEvent to determine whether it needs to intercept an event. If the method returns true, the View will consume the event, that is, the onTouchEvent() method will be called. If false is returned, the event is handled by calling dispatchTouchEvent() of the child View.
  • onTouchEvent() is also called in dispatchTouchEvent as onInterceptTouchEvent(). Used to handle click events, including ACTION_DOWN,ACTION_MOVE,ACTION_UP. If the result is false, it means that the event is not consumed and the next sequence of events is not intercepted. If returned to true, this event is consumed by the current View.
  • The OnTouchListener of View is emphasized here. If View sets the listener, OnTouch() will call back. If true is returned, the View's OnTouchEvent will not be executed because the OnTouchListener is set to perform at a higher priority than onTouchEvent. And onClickListener, which we are familiar with, has a lower priority than the two above.

These three methods are often encountered in dealing with View conflicts, so we should be familiar with their mechanisms. After looking at these three methods, let's look at the event distribution mechanism.

In order to be able to analyze clearly, we first build a new project, the layout is as follows:

ViewGroup1 nests ViewGroup2 and then CustomView (here I inherit from TextView). The layout is simple, just to illustrate the distribution process of ViewGroup.
Rewrite dispatchTouchEvent, OnInterceptTouchEvent, OnTouchEvent methods in the corresponding View (CustomView does not have OnInterceptTouchEvent), and output the corresponding Log in each method:

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("ViewGroup1","ViewGroup1 dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e("ViewGroup1","ViewGroup1 onInterceptTouchEvent");
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("ViewGroup1","ViewGroup1 onTouchEvent");
        return super.onTouchEvent(event);
    }

In CustomView:

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.e("CustomView", "CustomView dispatchTouchEvent");
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("CustomView", "CustomView onTouchEvent");
        return super.onTouchEvent(event);
    }

Run the program and click CustomView to get the following Log information:

E/ViewGroup1: ViewGroup1 dispatchTouchEvent
 E/ViewGroup1: ViewGroup1 onInterceptTouchEvent
 E/ViewGroup2: ViewGroup2 dispatchTouchEvent
 E/ViewGroup2: ViewGroup2 onInterceptTouchEvent
 E/CustomView: CustomView dispatchTouchEvent
 E/CustomView: CustomView onTouchEvent
 E/ViewGroup2: ViewGroup2 onTouchEvent
 E/ViewGroup1: ViewGroup1 onTouchEvent

As you can see from the Log information, the ViewGroup1 dispatchTouchEvent method is first executed. In the previous section, we talked about the internal logic of the dispatchTouchEvent () method:

public boolean dispatchTouchEvent(Motion e){
     boolean result=false;
     if(onInterceptTouchEvent(e)){
     //If the current View intercepts an event, the event is handled by the current View, which calls onTouchEvent()
        result=onTouchEvent(e);
     }else{
        //If not intercepted, send it to its child View for distribution
        result=child.dispatchTouchEvent(e);
     }
     return result;
}

After the ViewGroup1 dispatchTouchEvent method is called, onInterceptTouchEvent () is then called to determine whether events need to be intercepted, which is not intercepted by default. Events are passed to the child View of ViewGroup 1, which is ViewGroup 2. That is, the dispatchTouchEvent method of ViewGroup2 is called until CustomView. When an event is passed to CustomView, the dispatchTouchEvent method of CustomView, which is also CustomView, executes. As you can see, the distribution of the entire event is passed from ViewGroup 1 to CustomView. At this point, if CustomView cannot handle the change event, that is to say, the OnTouchView method of CustomView returns false, then the event is handed up to OnTouchEvent () event processing of ViewGroup 2, and so on:

If onInterceptTouchEvent () of ViewGroup2 returns to true, that is, to intercept events, what happens?

 @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e("ViewGroup2","ViewGroup2 onInterceptTouchEvent");
        return true;
    }

Operation results:

E/ViewGroup1: ViewGroup1 dispatchTouchEvent
E/ViewGroup1: ViewGroup1 onInterceptTouchEvent
E/ViewGroup2: ViewGroup2 dispatchTouchEvent
E/ViewGroup2: ViewGroup2 onInterceptTouchEvent
E/ViewGroup2: ViewGroup2 onTouchEvent
E/ViewGroup1: ViewGroup1 onTouchEvent

CustomView's dispatchTouchEvent() is not executed, and the event is not passed to CustomView, but the onTouchEvent of ViewGroup2 is directly invoked.

In fact, this is also very understandable, and our daily business process is the same, the company owner (ViewGroup 1) to the task of the manager (ViewGroup 2), the manager in the work of the staff (CustomView). Here, if the manager (ViewGroup 2) thinks it can be done by himself and intercepts the work (onInterceptTouch Event returns to true), he will no longer assign the task to the CustomView.
After the distribution process, let's look at the event response mechanism. We pass the event to CustomView and return the OnTouchEvent event of CustomView to true. The following results are obtained:

E/ViewGroup1: ViewGroup1 dispatchTouchEvent
E/ViewGroup1: ViewGroup1 onInterceptTouchEvent
E/ViewGroup2: ViewGroup2 dispatchTouchEvent
E/ViewGroup2: ViewGroup2 onInterceptTouchEvent
E/CustomView: CustomView dispatchTouchEvent
E/CustomView: CustomView onTouchEvent

We will find that ViewGroup 2 OnTouch Event will not be executed, or in our case just now, CustomView successfully completed the task after receiving the task, so it is not necessary to bother the boss to respond to the processing. Only when an employee (CustomView) is unable to complete the task (OnTouchEvent returns false) will the manager be asked to help (ViewGroup2 calls the OnTouchEvent method), similarly, if the manager can't do it, it will be left to the boss.

Posted by ksukat on Wed, 10 Apr 2019 03:15:32 -0700