preface

Every time I hear about event distribution, I feel very difficult and certainly not good to learn. In fact, event distribution is just that. This article will not tell you how much you know about event distribution, but it is absolutely fine to use it in simple application development, so let’s get started.

Android Touch event distribution is one of the essential skills for Android engineers. There are several main directions for in-depth analysis of event distribution:

  • 1. How the Touch event is passed from the driver layer to the Framework Layer InputManagerService;
  • 2. How WMS passes the event to the target window through ViewRootImpl;
  • 3. How Touch events are passed to inner subviews after they arrive in the DecorView.

The noun understanding

  • AMS: schedules the activities of all applications in a unified manner
  • WMS: Controls the display and hiding of all Windows and where to display them

What is an event

Event: Touch is an interactive event when a user touches the phone interface. Events include: press, slide, lift and cancel. These events are encapsulated as MotionEvent objects. The main events in this object are shown in the following table:

Flow of events

Pressing, sliding, lifting, and canceling events form a stream of events. The stream of events begins with a press and may have several slips in between, ending with a lift or cancel.

What is event distribution

Event distribution: Passing the click event contained in a complete click to a specific View or ViewGroup to process (consume) it. Distribution is passed from the top down, with objects that might pass through the highest-level Activity, the middle-level ViewGroup, and the lowest level View. For example, if you have a Button control on the screen, when you click it, the click event is sent from the Activity to the ViewGroup where the Button is, and then to the Button control to process it. That is, event distribution is traversed from top to bottom until you find a View or ViewGroup that can handle consuming the click event.

In the process of event distribution on Android, it mainly distributes Down events, and then finds the component that can handle Down events. Subsequent events in the event stream (such as Move, Up, and so on) are distributed directly to the component that can handle the press event. Therefore, the content discussed in this article is mainly for the press event.

Thinking to comb

Before diving into the event distribution source code, two concepts need to be made clear.

ViewGroup

A ViewGroup is a group of views, which may contain multiple child views. When a finger touches the screen, the area of the finger can be either within the scope of the ViewGroup display or on its internal View control. So its internal event distribution focuses on handling the logical relationship between the current ViewGroup and its subviews:

  • 1. Whether the current ViewGroup needs to intercept Touch events;
  • 2. Whether to continue distributing Touch events to subviews;
  • 3. How to distribute Touch events to subviews.

View

View is a simple control that can no longer be subdivided, and there will be no subviews inside, so its event distribution focuses on how the current View handles Touch events, and performs a series of effects (such as sliding, zooms in, clicking, long pressing, etc.) according to the corresponding gesture logic.

  • 1. Check whether a TouchListener exists.
  • 2. Whether to receive and process touch events (the main logic is in the onTouchEvent method).

Methods involved in event distribution

Simple usage parsing of methods

We can see that the return value of all three methods is of type Boolean. In fact, they use the return value to determine the direction of the next pass.

1. DispatchTouchEvent () — Used to distribute events

This method distributes Touch events to children from top to bottom until they are terminated or reach the View layer. This method also distributes Touch events in a tunnel mode. In this case, onInterceptTouchEvent() and onTouchEvent() are called, usually not overwritten.

  • Return false to continue distribution without interception;
  • Return true to intercept the event from being distributed to the underlying element;

False is returned by default in the dispatchTouchEvent() method.

2, onInterceptTouchEvent() — Used to intercept the event

  • If false is returned and the event is not intercepted, the Touch event is passed down to its children.
  • If true is returned, the event will be intercepted and handled by the current ViewGroup, calling the ViewGroup’s onTouchEvent() method.

3. OnTouchEvent () — To handle the event

  • Returning true means that the View can handle the event, and the event will stop passing up (to its parent View).
  • Returns false to indicate that it cannot be handled, and passes the event to its parent View’s onTouchEvent() method for processing.

The class that owns the above methods

Note: The ViewGroup has an additional onInterceptTouchEvent() method, and the other two methods are shared by the three types.

Event distribution Process

After a single event is triggered, the event distribution process is Activity①>ViewGroup②>View③, as shown in the figure below:

To fully understand the Android distribution mechanism, it is essential to understand:

  • The mechanism by which an Activity distributes click events
  • ViewGroup distribution mechanism for click events
  • View’s distribution mechanism for click events

It can be found from the U-shaped diagram that the parent component distributes to the child component continuously. If the child component can handle it, it will return immediately. If none of the children are processed, it is passed to the underlying children and back again. Distributing events across the entire View is essentially a big recursive function.

The instance

Let’s write a simple example to better understand this big U-shaped diagram:

Create an instance

Create MyViewGroup inherit from ViewGroup

Override the dispatchTouchEvent(), onInterceptTouchEvent(), and onTouchEvent() methods

public class MyViewGroup extends RelativeLayout {
    public MyViewGroup(Context context) {
        super(context);
    }

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        MLog.logEvent("MyViewGroup.dispatchTouchEvent:",ev);
        return super.dispatchTouchEvent(ev);
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        MLog.logEvent("MyViewGroup.onInterceptTouchEvent:",ev);
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        MLog.logEvent("MyViewGroup. OnTouchEvent handles itself :",event);
        return super.onTouchEvent(event); }}Copy the code

Create MyView to inherit from View

Override the dispatchTouchEvent() and onTouchEvent() methods

public class MyView extends View {
    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        MLog.logEvent("MyView.dispatchTouchEvent:",ev);
        return super.dispatchTouchEvent(ev);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        MLog.logEvent("MyView.onTouchEvent:",event);
        return super.onTouchEvent(event); }}Copy the code

Create TouchActivity inherit Activity

Override the dispatchTouchEvent() and onTouchEvent() methods

public class TouchActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_touch);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        MLog.logEvent("TouchActivity.dispatchTouchEvent:",ev);
        return super.dispatchTouchEvent(ev);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        MLog.logEvent("TouchActivity.onTouchEvent:",event);
        return super.onTouchEvent(event); }}Copy the code

Creating a layout file

Add controls MyViewGroup and MyView


      
<com.scc.demo.view.MyViewGroup xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mtrg_touch"
    android:layout_width="match_parent"
    android:layout_height="300dp"
    android:background="@color/color_FF773D">
    <com.scc.demo.view.MyView
        android:id="@+id/mtv_onclick"
        android:layout_width="120dp"
        android:layout_height="60dp"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="40dp"
        android:background="@color/color_188FFF"/>
</com.scc.demo.view.MyViewGroup>
Copy the code

MLog.logEvent()

You can view corresponding events

public static void logEvent(String msg, MotionEvent event) {
        String motionEvent = "";
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:// When the screen is pressed (the start of all events)
                motionEvent="DOWN";
                break;
            case MotionEvent.ACTION_UP:// When the screen is up (corresponding to DOWN)
                motionEvent="UP";
                break;
            case MotionEvent.ACTION_MOVE:// When swiping on the screen
                motionEvent="MOVE";
                break;
            case MotionEvent.ACTION_CANCEL:// When sliding outside the control boundary
                motionEvent="CANCEL";
                break;
        }
        Log.e("SccEvent", msg + motionEvent);
    }
Copy the code

Click on the page to see what happens

Click on Activity(white area)

The results

E/SccEvent: TouchActivity.dispatchTouchEvent:DOWN
E/SccEvent: TouchActivity.onTouchEvent:DOWN
E/SccEvent: TouchActivity.dispatchTouchEvent:MOVE
E/SccEvent: TouchActivity.onTouchEvent:MOVE
E/SccEvent: TouchActivity.dispatchTouchEvent:MOVE
E/SccEvent: TouchActivity.onTouchEvent:MOVE
E/SccEvent: TouchActivity.dispatchTouchEvent:UP
E/SccEvent: TouchActivity.onTouchEvent:UP
Copy the code

ViewGroup(yellow area)

The results

E/SccEvent: TouchActivity.dispatchTouchEvent:DOWN
E/SccEvent: MyViewGroup.dispatchTouchEvent:DOWN
E/SccEvent: MyViewGroup.onInterceptTouchEvent:DOWN
E/SccEvent: MyViewGroup.onTouchEvent:DOWN
E/SccEvent: TouchActivity.onTouchEvent:DOWN
E/SccEvent: TouchActivity.dispatchTouchEvent:MOVE
E/SccEvent: TouchActivity.onTouchEvent:MOVE
E/SccEvent: TouchActivity.dispatchTouchEvent:MOVE
E/SccEvent: TouchActivity.onTouchEvent:MOVE
E/SccEvent: TouchActivity.dispatchTouchEvent:UP
E/SccEvent: TouchActivity.onTouchEvent:UP
Copy the code

I’m gonna go to View(blue area)

The results

E/SccEvent: TouchActivity.dispatchTouchEvent:DOWN
E/SccEvent: MyViewGroup.dispatchTouchEvent:DOWN
E/SccEvent: MyViewGroup.onInterceptTouchEvent:DOWN
E/SccEvent: MyView.dispatchTouchEvent:DOWN
E/SccEvent: MyView.onTouchEvent:DOWN
E/SccEvent: MyViewGroup.onTouchEvent:DOWN
E/SccEvent: TouchActivity.onTouchEvent:DOWN
E/SccEvent: TouchActivity.dispatchTouchEvent:MOVE
E/SccEvent: TouchActivity.onTouchEvent:MOVE
E/SccEvent: TouchActivity.dispatchTouchEvent:MOVE
E/SccEvent: TouchActivity.onTouchEvent:MOVE
E/SccEvent: TouchActivity.dispatchTouchEvent:UP
E/SccEvent: TouchActivity.onTouchEvent:UP
Copy the code

Results analysis

The more overlapping areas, the more events will be triggered. This is where event distribution comes in.

Click on the blue area and walk through a large U distribution without anyone intercepting and handling it.

If you look at the above log, you will see that the press event goes through so many methods. Why does the swipe and lift only call the methods in the Activity?

The reason is that the distribution of the MOVE, UP, and other events depends on who captured their starting event Down. See why DOWN events are special later in this article

All Touch events will be printed this time, and only press events will be printed later.

Event distribution and processing

Activity processing and distribution

To deal with

1. Will MyViewGroup. DispatchTouchEvent () returns to modify to false

  @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        MLog.logEvent("MyViewGroup.dispatchTouchEvent:",ev);
        return false;
    }
Copy the code

The results

E/SccEvent: TouchActivity.dispatchTouchEvent:DOWN E/SccEvent: MyViewGroup.dispatchTouchEvent:DOWN E/SccEvent: TouchActivity. OnTouchEvent: DOWN E/SccEvent: TouchActivity. SccEvent play right hereCopy the code

2.MyViewGroup only intercepts and does not process, but also gives Activity. OnTouchEvent processing

3. The event is not processed by ViewGroup or View, and is finally handed over to Activity. OnTouchEvent (e.g. click on the blue area above to analyze the result).

distribution

Actvitiy is the top layer of event dispatch. The dispatchTouchEvent method returns true. Return false or super.DispatchTouchEvent (EV) to distribute the event down. So you don’t have to handle (rewrite)dispatchTouchEvent()

ViewGroup intercept processing and distribution

Intercept processing

To ensure the Activity after the well distributed (i.e., no dispatchTouchEvent modifications), modified MyViewGroup. OnInterceptTouchEvent () and MyViewGroup onTouchEvent () method, The focus of the ViewGroup is onInterceptTouchEvent, so override both the intercepting and handling methods.

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        MLog.logEvent("MyViewGroup.dispatchTouchEvent:",ev);
        return super.dispatchTouchEvent(ev);
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        MLog.logEvent("MyViewGroup.onInterceptTouchEvent:",ev);
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        MLog.logEvent("MyViewGroup.onTouchEvent:",event);
        MLog.logEvent("Myviewgroup.sccevent is playing right here.",event.getAction());
        return true;
    }
Copy the code

The results

E/SccEvent: TouchActivity.dispatchTouchEvent:DOWN E/SccEvent: MyViewGroup.dispatchTouchEvent:DOWN E/SccEvent: MyViewGroup. OnInterceptTouchEvent: DOWN E/SccEvent: MyViewGroup. OnTouchEvent: DOWN E/SccEvent: MyViewGroup. SccEvent play right hereCopy the code

Results analysis

The event is handled in the myViewGroup.onTouchEvent () method, and MyViewGroup tells the TouchActivity that it’s handled. At this point it doesn’t matter whether MyView wants to handle it or not, because the event has made MyViewGroup skip.

If this time to return to the super onTouchEvent (event), and this is the default processing, later to TouchActivity. OnTouchEvent () to deal with. See the effect

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        MLog.logEvent("MyViewGroup.onTouchEvent:",event);
        MLog.logEvent("Myviewgroup.sccevent is playing right here.",event.getAction());
        return super.onTouchEvent(event);
    }
Copy the code

The results

E/SccEvent: TouchActivity.dispatchTouchEvent:DOWN E/SccEvent: MyViewGroup.dispatchTouchEvent:DOWN E/SccEvent: MyViewGroup.onInterceptTouchEvent:DOWN E/SccEvent: MyViewGroup.onTouchEvent:DOWN E/SccEvent: MyViewGroup. SccEvent is here to play TouchActivity. OnTouchEvent: DOWNCopy the code

Then it’s a failed interception and handling.

distribution

Do not modify the ViewGroup dispatchTouchEvent() and onInterceptTouchEvent()

View processing and distribution

The View to deal with

After making sure the ViewGroup is ready to distribute, modify the myView.onTouchEvent () method.

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        MLog.logEvent("MyView.dispatchTouchEvent:",ev);
        return super.dispatchTouchEvent(ev);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        MLog.logEvent("MyView.onTouchEvent:",event);
        MLog.logEvent("Myview.sccevent is here to play.",event.getAction());
        return true;
// return super.onTouchEvent(event);
    }
Copy the code

The results

E/SccEvent: TouchActivity.dispatchTouchEvent:DOWN E/SccEvent: MyViewGroup.dispatchTouchEvent:DOWN E/SccEvent: MyViewGroup.onInterceptTouchEvent:DOWN E/SccEvent: MyView.dispatchTouchEvent:DOWN E/SccEvent: Myview.ontouchevent :DOWN E/SccEvent: MyView.sccEvent is here to playCopy the code

Results analysis

So myView.onTouchEvent () handles the event, so MyTouchView tells MyViewGroup THAT I’ve consumed, and MyViewGroup tells TouchActivity that I’ve consumed.

distribution

The View itself is the smallest View, no children View gave him a distribution, so the MyView. DispatchTouchEvent () method can not do change, or don’t rewrite.

The difference between onTouch() and onTouchEvent()

OnTouchEvent () method:

OnTouchEvent is a method of handling the events on the phone screen. It is a method of obtaining the various actions on the screen, such as swiping left and right, hitting the back button, etc. This method is not called when there is a touch event on the screen.

The onTouch method:

OnTouch () is the method of the OnTouchListener interface. It is used to get the touch event of a control, so you must bind to the control using setOnTouchListener before you can identify the touch event of the control.

summary

OnTouch () has a higher priority than onTouchEvent() and will fire first.

If the onTouch method returns false, the onTouchEvent will be triggered, otherwise the onTouchEvent method will not be called.

Events such as onClick are implemented based on onTouchEvent, and if onTouchEvent returns true, these events will not be triggered.

The above assumes ViewGroup distribution.

The above situations basically cover the scenarios that occur in the application, and you can also use Touch event distribution correctly with proper use. Isn’t that simple.

Why is the DOWN event special

All Touch events start with the DOWN event, which is one of the reasons why DOWN events are special. Another reason is that the processing result of the DOWN event directly affects the logic of subsequent MOVE and UP events. In other words, subsequent MOVE, UP and other events are distributed to whom, depending on who captured their initial event Down.

  • Slide up: Consolidate by looking at simple usage parsing of the method
  • Swipe down: View source code Analysis for further learning

Source code analysis

Below, we together through the source code, a comprehensive analysis of the event distribution mechanism, that is, in order to explain:

  • Activity Event distribution mechanism
  • ViewGroup Event distribution mechanism
  • View Event distribution mechanism

Activity Event distribution mechanism

The Android event dispatch mechanism first passes a click event to an Activity by executing dispatchTouchEvent().

Activity. DispatchTouchEvent () the source code

/ * * * founder: handsome * create time: 2021/7/5 * function: Activity. The dispatchTouchEvent () * /
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
	// This is only used for ACTION_DOWN judgment
	onUserInteraction();
    }
    / / return true
    if (getWindow().superDispatchTouchEvent(ev)) {
 	. / / the Activity dispatchTouchEvent () returns true, the method to an end.
  	// This click stops the event passing down & the event passing process ends
  	return true;
    }
    return onTouchEvent(ev);
}
Copy the code

Activity. OnUserInteraction () the source code

Function: This method is a user interaction that is called whenever a key, touch, or trackball event is assigned to the Activity. * /
public void onUserInteraction(a) {}Copy the code

Window. SuperDispatchTouchEvent () the source code

/ * * * founder: handsome * create time: 2021/7/6 * functions: Window. SuperDispatchTouchEvent belong to the abstract methods. * Used for custom Windows, such as Dialog, that pass touchscreen events further down the view hierarchy. * Application developers should not need to implement or call it. * /
public abstract boolean superDispatchTouchEvent(MotionEvent event);
Copy the code

Since Window is an abstract class, we dug further and found its only implementation, PhoneWindow.

PhoneWindow. SuperDispatchTouchEvent () the source code

// This is the top-level view of the window, containing the window decor.
// This is the instance object of the window's top View.
private DecorView mDecor;
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
	// Let's move on
	return mDecor.superDispatchTouchEvent(event);
}
Copy the code

DecorView. SuperDispatchTouchEvent () the source code

public boolean superDispatchTouchEvent(MotionEvent event) {
	// Super calls the superclass dispatchTouchEvent method. And who is its parent?
	DecorView extends FrameLayout; FrameLayout extends ViewGroup!
	DecorView is an indirect subclass of ViewGroup.
	/ / see the Activity here. DispatchTouchEvent () is also the basic about the same
	/ / if ViewGroup dispatchTouchEvent return true,
	/ / the Activity. DispatchTouchEvent () to return true.
	/ / if ViewGroup dispatchTouchEvent return false,
	// Then execute activity.onTouchEvent (ev).
	return super.dispatchTouchEvent(event);
}
Copy the code

Source Activity. OnTouchEvent ()

Function: called when the Touch event has not been consumed by any View below it. * /
public boolean onTouchEvent(MotionEvent event) {
    / / the Window. To judge whether shouldCloseOnTouch consumption.
    / / that we can continue to look at the Window. The shouldCloseOnTouch is doing
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        // If the event has been consumed, return true
        return true;
    }
    // No consumption has been made. False by default
    return false;
}
Copy the code

Window. ShouldCloseOnTouch () the source code

MaxTargetSdk = build.version_codes.p (28);
If the event is a DOWN event, if the event coordinates are within the boundary, etc
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
    final boolean isOutside =
            event.getAction() == MotionEvent.ACTION_UP && isOutOfBounds(context, event)
            || event.getAction() == MotionEvent.ACTION_OUTSIDE;
    if(mCloseOnTouchOutside && peekDecorView() ! =null && isOutside) {
	// Return true: indicates that the event is outside the boundary, that is, consuming the event
	return true;
    }
// Return false: within bounds, that is, no consumption (default)
    return false;
}
Copy the code

And that’s basically the end of the activity.onTouchEvent (). After the source perfect in the supplement.

Activity Source summary

When a click event occurs, starting from the event distribution of the Activity (the Activity. The dispatchTouchEvent ()), the process is as follows:

ViewGroup Event distribution mechanism

From the above event distribution mechanism of the Activity, the Activity. The dispatchTouchEvent () the event from the Activity – > ViewGroup pass, The event dispatch mechanism for ViewGroup starts with dispatchTouchEvent().

In the Activity. The dispatchTouchEvent () left ViewGroup. DispatchTouchEvent () when I return true/false in the following source code analysis to find out.

ViewGroup. DispatchTouchEvent () the source code

From a macro perspective, the entire Dispatch source code is as follows:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    OnInterceptTouchEvent = onInterceptTouchEvent; onInterceptTouchEvent = onInterceptTouchEvent; * 2, mFirstTouchTarget! =null, meaning that a subview has captured the event, and a subview dispatchTouchEvent that returns true means that the touch has been captured. * /
    /** * select * from subview where canceled and intercepted are set to false. ACTION_DOWN indicates that events are actively distributed when events are DOWN. * 2. Through the for loop, traversing all subviews under the current ViewGroup; * 3. Determine whether the event coordinate is within the range of the subview coordinate, and the subview is not in animation state; * 4, call dispatchTransformedTouchEvent method distributed event to View, if the child View successful capture events, will be mFirstTouchTarget assigned to the View. * /
    If mFirstTouchTarget is null, no subview has captured the event in the previous event distribution. Direct call dispatchTransformedTouchEvent method, and introduced to the child is null will eventually call super. DispatchTouchEvent method. It actually ends up calling its own onTouchEvent method to handle touch events. Conclusion: If no child View captures and handles touch events, the ViewGroup handles them through its own onTouchEvent method. * 3.2 if mFirstTouchTarget is not null, it indicates that there is a child View that captured the touch event in the above event distribution. Therefore, the current and subsequent events are directly handed over to the View pointed to by mFirstTouchTarget for processing. * /
}
Copy the code

Below we layer by layer analysis:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    // Whether to press the operation, the final external return result, the final return value of the method
    boolean handled = false;
    / * onFilterTouchEventForSecurity filtering touch events on application security strategies. /return true Dispatches the event, return false deletes the event. * /
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;
        final boolean intercepted;
        OnInterceptTouchEvent = onInterceptTouchEvent; onInterceptTouchEvent = onInterceptTouchEvent; * 2, mFirstTouchTarget! =null, meaning that a subview has captured the event, and a subview dispatchTouchEvent that returns true means that the touch has been captured. * /
        if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
            final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
            if(! disallowIntercept) { intercepted = onInterceptTouchEvent(ev);// Restore operations in case it is changed
                ev.setAction(action);
            } else {
                intercepted = false; }}else {
            // There are no touch targets and this action is not an initial down
            // so this view group continues to intercept touches.
            intercepted = true;
        }
        // Check for cancelation.
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        // Update list of touch targets for pointer down, if needed.
        final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
        final booleansplit = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) ! =0
                && !isMouseEvent;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        /** * assign the event to the subview * if both canceled and intercepted are false, neither canceled nor intercepted */
        if(! canceled && ! intercepted) {/*1, actionMasked==MotionEvent.ACTION_DOWN indicates that events are actively distributed if they are DOWN events */
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                final int actionIndex = ev.getActionIndex(); // always 0 for down
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                        : TouchTarget.ALL_POINTER_IDS;

                // Clean up earlier touch targets for this pointer id in case they
                // have become out of sync.
                removePointersFromTouchTargets(idBitsToAssign);

                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null&& childrenCount ! =0) {
                    final float x =
                            isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
                    final float y =
                            isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
                    // Find a child that can receive the event.
                    // Scan children from front to back.
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    //2. Loop through all subviews under the current ViewGroup;
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);
                        //3, determine whether the event coordinates are within the range of the subview coordinates, and the subview is not in the animation state;
                        if(! child.canReceivePointerEvents() || ! isTransformedTouchPointInView(x, y, child,null)) {
                            continue;
                        }

                        newTouchTarget = getTouchTarget(child);
                        if(newTouchTarget ! =null) {
                            // Child is already receiving touch within its bounds.
                            // Give it the new pointer in addition to the ones it is handling.
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }

                        resetCancelNextUpFlag(child);
                        / * 4, call dispatchTransformedTouchEvent method distributed event to View, if the child View successful capture events, will be mFirstTouchTarget assigned to the View. * /
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // Child wants to receive touch within its bounds.
                            mLastTouchDownTime = ev.getDownTime();
                            if(preorderedList ! =null) {
                                // childIndex points into presorted list, find original index
                                for (int j = 0; j < childrenCount; j++) {
                                    if (children[childIndex] == mChildren[j]) {
                                        mLastTouchDownIndex = j;
                                        break; }}}else {
                                mLastTouchDownIndex = childIndex;
                            }
                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break; }}if(preorderedList ! =null) preorderedList.clear(); }}}/** * select * from mFirstTouchTarget
        / / 3.1 mFirstTouchTarget is null,
        if (mFirstTouchTarget == null) {
            /* There is no subview that captures the event. Direct call dispatchTransformedTouchEvent method, and introduced to the child is null will eventually call super. DispatchTouchEvent method. It actually ends up calling its own onTouchEvent method to handle touch events. * /
            /* Conclusion: If there are no child views to capture and handle touch events, the ViewGroup will handle them through its own onTouchEvent method. * /
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            // Dispatch to touch targets, excluding the new touch target if we already
            // dispatched to it. Cancel touch targets if necessary.
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            while(target ! =null) {
                final TouchTarget next = target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;

                    /* If mFirstTouchTarget is not null, it indicates that a child View captured the touch event in the above event distribution, so it directly assigns the current and subsequent events to the View pointed to by mFirstTouchTarget for processing. * /
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true; } } predecessor = target; target = next; }}}return handled;
}
Copy the code

ViewGroup. OnInterceptTouchEvent () the source code

/ * * * founder: handsome * create time: 2021/7/6 * premise ViewGroup dispatchTouchEvent return. Super dispatchTouchEvent (ev); Return true intercepts events, return false(default) does not intercept events */
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
            && ev.getAction() == MotionEvent.ACTION_DOWN
            && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
            && isOnScrollbarThumb(ev.getX(), ev.getY())) {
        return true;
    }
    return false;
}
Copy the code

ViewGroup. OnTouchEvent () the source code

ViewGroup does not have an onTouchEvent() method, because ViewGroup is a subclass of View, so ViewGroup can call the onTouchEvent() method of View directly. Here do not do detailed introduction, the following in View event distribution mechanism together to answer.

ViweGroup source code summary

After Android event distribution is passed to Acitivity, it is always passed to ViewGroup first and then to View. The process is summarized as follows :(assuming that the Acitivity event has been passed and passed to the ViewGroup)

View event distribution mechanism

From the ViewGroup event dispatch mechanism above, View event dispatch mechanism starts with dispatchTouchEvent().

The dispatchTouchEvent () the source code

public boolean dispatchTouchEvent(MotionEvent event) {
    (mViewFlags & ENABLED_MASK) == ENABLED (mViewFlags & ENABLED_MASK) == ENABLED (mViewFlags & ENABLED_MASK) == ENABLED (mViewFlags & ENABLED_MASK) == ENABLED (mViewFlags & ENABLED_MASK) == ENABLED (mViewFlags & ENABLED_MASK) == ENABLED (mViewFlags & ENABLED_MASK

    /* select * from the listener where the listener is. = null 1, the mOnTouchListener variable is assigned to view.setonTouchListener () 2, that is, as long as the Touch event is registered for the control, mOnTouchListener must be assigned (that is, not null) */

     /* 3, montouchListener.ontouch (this, event) = onTouch(); 2. Manual copy setting is required. View.setontouchlistener (new OnTouchListener() {@override public Boolean onTouch(View v, MotionEvent event) {mlog.logEvent ("MyTouchTrueView.onTouch ",event); return true; // if onTouch() returns true, then all of the above conditions are true, so that view.dispatchTouchEvent () returns true and the event is dispatched. // 3, onTouchEvent() source code analysis}}); // 3, onTouchEvent() source code analysis; * /
    if( (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener ! =null &&
            mOnTouchListener.onTouch(this, event)) {
        return true;
    }

    return onTouchEvent(event);
}
Copy the code

The onTouchEvent () the source code

public boolean onTouchEvent(MotionEvent event) {...// Show only the key code
    // If the control is clickable, the switch judgment is entered
    if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
        // Handle the event based on the current event type
        switch (event.getAction()) { 
            / / to lift the View
            case MotionEvent.ACTION_UP:  
                    performClick(); 
                    break;  
            / / press
            case MotionEvent.ACTION_DOWN:  
                postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
                break;  
            / / cancel
            case MotionEvent.ACTION_CANCEL:  
                refreshDrawableState();  
                removeTapCallback();  
                break;
            / / sliding
            case MotionEvent.ACTION_MOVE:  
                final int x = (int) event.getX();  
                final int y = (int) event.getY();  
                int slop = mTouchSlop;  
                if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
                        (y < 0 - slop) || (y >= getHeight() + slop)) {  
                    removeTapCallback();  
                    if((mPrivateFlags & PRESSED) ! =0) { removeLongPressCallback(); mPrivateFlags &= ~PRESSED; refreshDrawableState(); }}break;  
        }  

        // If the control is clickable, it must return true
        return true;  
    }  
  // If the control is not clickable, it must return false
  return false;  
}
  public boolean performClick(a) {  
      if(mOnClickListener ! =null) {
          // Just register a click event for the control View via setOnClickListener()
          // Then the mOnClickListener variable will be assigned a value (i.e., not null)
          OnClick () & performClick() returns true
          playSoundEffect(SoundEffectConstants.CLICK);  
          mOnClickListener.onClick(this);  
          return true;  
      }  
      return false;  
  }  
Copy the code

View source summary

After Android event distribution is passed to Acitivity, it is always passed to ViewGroup first and then to View. The process is summarized as follows :(assuming that the ViewGroup event distribution has passed and passed to the View)

conclusion

The process mechanism of dispatchTouchEvent event is mainly analyzed, which is mainly divided into three parts:

  • 1. Determine whether intercepting is required — > Mainly decide whether to intercept according to the return value of onInterceptTouchEvent method;
  • If a child View captures and consumes a touch event, then assign a value to the mFirstTouchTarget; if a child View captures and consumes a touch event, then assign a value to the mFirstTouchTarget.
  • 3. Finally, the DOWN, MOVE, and UP events are determined by whether the mFirstTouchTarget is null or not.

It then introduces a few special points in the overall event distribution.

  • 1. What is special about the DOWN event: the starting point of the event; Decide who will consume and deal with subsequent events;
  • MFirstTouchTarget is a linked list of views that capture consumption touch events.
  • 3. Trigger scenario of CANCEL event: When the superview does not intercept first and then intercepts again in the MOVE event, the child View will receive a CANCEL event at this time.

That’s all for this article, hoping to help and inspire you to learn about Android event distribution.