Android event delivery mechanism (1) process combing

Event delivery or event distribution is a very important and core knowledge point in Android. Any click or touch on our mobile phone will involve event delivery, so it is very necessary to have a good understanding of relevant knowledge.

When a Touch event occurs, first the current Activity gets the event, then Activity will pass the event to Window, and then Window will pass the event to the root layout of the current interface (top-level parent View), that is, in accordance with Activity -> Window -> View order, and finally the top-level parent View passes the event to the child View according to the event delivery mechanism, which is the focus of this article.

First clarify a few concepts:

1. The event we are discussing refers to TouchEvent, which can be called a touch event, which includes ACTION_DOWN (press), ACTION_MOVE (move), ACTION_UP (lift), ACTION_CANCEL, etc. Under normal circumstances, the act of touching the mobile phone screen once starts with an ACTION_DOWN event, then generates a series of ACTION_MOVE events, and usually ends with an ACTION_UP event at the end.

2. The event delivery process corresponds to three very important methods: dispatchTouchEvent (MotionEvent ev) is used to distribute events; onInterceptTouchEvent (MotionEvent ev) is used to determine whether to intercept an event; onTouchEvent (MotionEvent event) responds to the event. If an event reaches a certain View or ViewGroup, the dispatchTouchEvent(MotionEvent ev) method of this control will be called first.

We know that ViewGroup inherits from View in Android, and ViewGroup can contain many sub-Views and sub-VewGroups. As shown in the figure, we imagine that there is a ViewGroupA that contains a ViewGroupB, and ViewGroupB contains a child View. Then a touch event is executed on the child View, then what kind of event delivery logic will be executed?

For related articles on the Internet, most bloggers use the method of analyzing the source code. I have also read the source code for several days, but today I decided to use another idea to sort out the process of event delivery.

In order to verify the logic of getting the event delivery, we customize the three controls ViewGroupA, ViewGroupB, and SonView respectively. Among them, ViewGroupA and ViewGroupB all inherit from ViewGroup, and SonView directly inherits from View:

package com.mliuxb.touchevent;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

/**
 * Description:自定义ViewGroupA
 */
public class ViewGroupA extends ViewGroup {
    
    public ViewGroupA(Context context) {
        super(context);
    }

    public ViewGroupA(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ViewGroupA(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public ViewGroupA(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //ViewGroupA的第一个子布局,位于上半部分,大小为二分之一
        View child = getChildAt(0);
        child.layout(l, t, r, b / 2);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(Constant.TAG, "ViewGroupA...dispatchTouchEvent: ");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i(Constant.TAG, "ViewGroupA...onInterceptTouchEvent: ");
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(Constant.TAG, "ViewGroupA...onTouchEvent: ");
        return super.onTouchEvent(event);
    }
}
package com.mliuxb.touchevent;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

/**
 * Description:自定义ViewGroupB
 */
public class ViewGroupB extends ViewGroup {

    public ViewGroupB(Context context) {
        super(context);
    }

    public ViewGroupB(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ViewGroupB(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public ViewGroupB(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //ViewGroupB的第一个子布局,位于布局中心,大小为四分之一
        View child = getChildAt(0);
        child.layout(r / 4, b / 4, r / 4 * 3, b / 4 * 3);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(Constant.TAG, "ViewGroupB...dispatchTouchEvent: ");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i(Constant.TAG, "ViewGroupB...onInterceptTouchEvent: ");
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(Constant.TAG, "ViewGroupB...onTouchEvent: ");
        return super.onTouchEvent(event);
    }
}
package com.mliuxb.touchevent;

import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

/**
 * Description:自定义SonView
 */
public class SonView extends View {

    public SonView(Context context) {
        super(context);
    }

    public SonView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public SonView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public SonView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i(Constant.TAG, "SonView...dispatchTouchEvent: ");
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(Constant.TAG, "SonView...onTouchEvent: ");
        return super.onTouchEvent(event);
    }
}

It should be noted that, inheriting ViewGroup must override the onLayout() method and set the layout of the related child controls. For the convenience of me, the layout of the child controls is written as fixed in ViewGroupA and ViewGroupB.

Then rewrite the dispatchTouchEvent(), onInterceptTouchEvent(), and onTouchEvent() methods respectively. Among them, there is no onInterceptTouchEvent() method in the View, that is, the pure View does not need to determine whether to intercept the event (it cannot contain other Views, and naturally does not need to determine whether to intercept the event), so SonView does not have the onInterceptTouchEvent() method. And print the relevant log in each method (set "TouchEvent" as a unified log TAG).

Then directly reference three custom controls in the layout file according to the scenario we envisioned:

<?xml version="1.0" encoding="utf-8"?>
<com.mliuxb.touchevent.ViewGroupA
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.mliuxb.touchevent.ViewGroupB
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorB">
        
        <com.mliuxb.touchevent.SonView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/colorC"/>

    </com.mliuxb.touchevent.ViewGroupB>

</com.mliuxb.touchevent.ViewGroupA>

As above, in order to distinguish each control, I set a different background for ViewGroupB and SonView. At this time, no additional settings are required in the Activity. The effect of running the project is as follows:

At this point, we tap the SonView area (the red area), and then we can get the following log results:

It can also be seen from the log: if an event reaches a certain View or ViewGroup, it will be the first to call the dispatchTouchEvent() method of this control to distribute the event.

Observing the execution process of these methods, you can get the following basic event delivery flowchart:

Through the above process, we can clearly see that the direction of event delivery is: from top to bottom from the parent control to the child control. By default, it will be passed down layer by layer until the last child control; the direction of event response is: from the child control to the parent control from bottom to top.

We all know that the onInterceptTouchEvent() method is used to determine whether to intercept an event, and the onTouchEvent() method responds to the event. Our return values ​​above are the return values ​​of the default super method. At this time, we can print the return values ​​of the onInterceptTouchEvent() and onTouchEvent() methods in ViewGroupA, ViewGroupB, and SonView. Modify the ViewGroupA log to print as follows:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(Constant.TAG, "ViewGroupA...dispatchTouchEvent: ");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean onInterceptTouchEvent = super.onInterceptTouchEvent(ev);
        Log.i(Constant.TAG, "ViewGroupA...onInterceptTouchEvent: " + onInterceptTouchEvent);
        return onInterceptTouchEvent;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean onTouchEvent = super.onTouchEvent(event);
        Log.i(Constant.TAG, "ViewGroupA...onTouchEvent: " + onTouchEvent);
        return onTouchEvent;
    }

The modification of ViewGroupB is similar to that of SonView, which is not shown here. Also touch the SonView area to get the following log results:

It can be seen that the system default onInterceptTouchEvent() method returns false, that is, the event is not intercepted by default, and the event is passed to the child control for processing. And since none of our three custom controls handle events, the onTouchEvent() method all returns false by default, and returns the event to the parent control for processing. Since they are all boolean return values, there are only two possibilities. Let's try to replace each return value with true.

Analyze various situations and print the log results. Combining the functions of each method, we can get the following complete event delivery flowchart:

Event delivery flowchart

(When the onTouchEvent() method returns true to indicate that the event is processed, the log process will be printed multiple times. This is because the TouchEvent mentioned at the beginning of the article contains ACTION_DOWN, ACTION_MOVE, ACTION_UP and other events. When the onTouchEvent() method returns true, ACTION_MOVE, ACTION_UP, etc. Subsequent events will be passed to the corresponding control, this part is subject to follow-up analysis)

According to our log and the above event delivery flowchart, we can get the following conclusions:

The direction of event delivery is: from top to bottom from the parent control to the child control;

1. ViewGroupA: First, the event must first reach the top-level parent View (ie ViewGroupA). When the event reaches ViewGroupA, its dispatchTouchEvent() method will be called first, and then the method will call its own onInterceptTouchEvent() method to determine whether it is This event needs to be intercepted. If true is returned, it means that the event is intercepted and the event is handled by itself, so at this time, its onTouchEvent() method will be called to respond to this event.

If it returns false, it means that the event is not intercepted, then child.dispatchTouchEvent(event) will be called to pass the event to the child control, and there will be a flow of subsequent events being passed down. The onInterceptTouchEvent() method returns false by default.

2. ViewGroupB: ViewGroupB, the child control of ViewGroupA, received this event. As analyzed above, the dispatchTouchEvent() method of ViewGroupB is called first. ViewGroupB is still a ViewGroup type, so the logic of continuing the event distribution is still the same as that of ViewGroupA, to call its own The onInterceptTouchEvent() method determines whether you need to intercept this event.

3. Child View: If the parent control does not intercept the event, the event will reach the child View. The dispatchTouchEvent() method of the child View is still called first. At this time, the logic of event distribution is slightly different, because View does not have an onInterceptTouchEvent method, because It does not need to determine whether to intercept the event. So when an event reaches the dispatchTouchEvent() method of this View, the dispatchTouchEvent() method cannot call the onInterceptTouchEvent() method. It will directly call the onTouchEvent() method and let the View directly respond to the event.

The direction of event response is: bottom-up from the child control back to the parent control;

4. The onTouchEvent() method also has a boolean return value, true or false. When the onTouchEvent() method of the child View is called, there are two possible return values, true or false. If it returns true, it means that the child View consumes this event, and the event terminates at this time. If it returns false, it means that the child View does not consume this event. At this time, the event will be passed back to the parent control, calling the onTouchEvent() method of the parent control ViewGroupB, and the parent control ViewGroupB will respond, then the parent control’s onTouchEvent( ) Method is the same logic, either consume this event, or pass back to the parent control ViewGroupA of the parent control. By analogy, if the event is not processed, the event will eventually be passed back to the Activity.

 

The above is the process of Android event delivery mechanism.
Demo code in the article: https://github.com/beita08/AndroidTouchEvent

Next article: Android event delivery mechanism (two) scenario analysis

Guess you like

Origin blog.csdn.net/beita08/article/details/88971864