This blog is suitable for readers

Someone who has experience in Android development, presumably knows about event distribution, and probably doesn’t. After reading and understanding this article can achieve: in the future to face the event to distribute the relevant content can be hard

preface

The Android event distribution mechanism is a cliche, and there are a lot of articles about it on the web. I have read a lot of blogs before, but I always resist this part of the content in my work, and when I tell others, I only have a little understanding of it haha (are you in the same state). The root cause is that the paper comes zhongjue shallow, must know this matter to Read the fucking source code. Today a little bit empty, decided to masturbate the relevant source code, deepen their understanding of this piece.

conclusion

Why jump to the conclusion? Want to let you know a conclusion, after reading the conclusion, and then follow the source rhythm to confirm the correctness of a conclusion. First, the methods associated with event distribution are as follows:

  • dispatchTouchEvent()

  • OnInterceptTouchEvent () only has this method in viewGroup

  • onTouchEvent()

Poof. You all know that, and then, the relationship between these three methods is something like this:

override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
    if(! onInterceptTouchEvent(ev)) {// This will call onTouchEvent() of the child view
        boolean childConsumed = child.dispathcTouchEvent()
        if(! childConsumed) {return onTouchEvent(ev)
        }
        return childConsumed
    }else {
        return onTouchEvent(ev)
    }
}
Copy the code

Simple flow chart:

Source code analysis

Next we follow the source code step by step, the core source code annotations are more detailed, so I overall introduction is less, ha ha ha

Tips: Open Android Studio in front of your computer and find the source code

//ViewGroup.java

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {...//actionDown is the beginning of a series of events that need to reset all states
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        cancelAndClearTouchTargets(ev);
        resetTouchState();
    }  

    final boolean intercepted;
    // If it is a Down event or mFirstTouchTarget is not null
    if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
        / / if the subclass does not call the parent. RequestDisallowInterceptTouchEvent (true) then the value to false
        final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
        / / if the subclass call requestDisallowInterceptTouch (true) judging the if condition is established The opposite will enter
        if(! disallowIntercept) {// Entering the viewGroup onInterceptTouchEvent() method returns false by default
            intercepted = onInterceptTouchEvent(ev);
            ev.setAction(action); // restore action in case it was changed
        } else {
            intercepted = false; }}else {
        intercepted is true if no child views consume events and the event is not a Down event
        intercepted = true; }...// Enter this code block if there is no cancel and the viewGroup itself does not intercept the event
    if(! canceled && ! intercepted) { ...// If the event is down, the code block is entered
        if (actionMasked == MotionEvent.ACTION_DOWN
            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {

            ...

            final int childrenCount = mChildrenCount;
            if (newTouchTarget == null&& childrenCount ! =0) {...final View[] children = mChildren;
                // Iterate backward through the child to see if the child consumes the event
                for (int i = childrenCount - 1; i >= 0; i--) {
                    ...
                    / / child. CanReceivePointerEvents () if the child can receive touch events Judgment conditions are visible and not perform the animation
                    / /! IsTransformedTouchPointInView (x, y, child, null) is used to judge touch events x, y is in the child of the rectangular area
                    // If one condition is not satisfied, the next child is traversed
                    if(! child.canReceivePointerEvents()|| ! isTransformedTouchPointInView(x, y, child,null)) {
                            continue; }...OnTouchEvent () is called within the view dispatchTouchEvent.
                     / / if the child view consumer events are dispatchTransformedTouchEvent () returns true into blocks of code execution logic
                     if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                         ...

                         / / assignment newTouchTarget
                         / / addTouchTarget () method will convenient mFirstTouchTarget assignment below series events directly to / / mFirstTouchTarget, child and call its dispatchTouchEvent method
                         newTouchTarget = addTouchTarget(child, idBitsToAssign);
                         // Set it to true for use in the following code
                         alreadyDispatchedToNewTouchTarget = true;
                         break; }}... }}if (mFirstTouchTarget == null) {
        // mFirstTouchTarget == null indicates no child View consumption event
        / / if no children view consumer events so call dispatchTransformedTouchEvent method directly after the results is actually called directly own onTouchEvent ()
        // Notice that the third argument is null and it calls super.dispatchTouchEvent(), which is the view's dispatchTouchEvent()
        // The view dispatchTouchEvent calls onTouchEvent
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);
    } else {
        // If mFirstTouchTarget is not null (which means that a child view consumed the event), enter this code block
        TouchTarget predecessor = null;
        TouchTarget target = mFirstTouchTarget;
        while(target ! =null) {
            final TouchTarget next = target.next;
            / / the down if there is a child view consumption when events will alreadyDispatchedToNewTouchTarget value to true and
            // Target and newTouchTarget address values are the same
            if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                handled = true;
            } else { // All other events (except down) use this code block
                // If the viewGroup intercepts the event, cancelChild will be true and the event. Action of the child view will be ACTION_CANCEL
                final boolean cancelChild = resetCancelNextUpFlag(target.child)
                        || intercepted;
                // Continue calling the child's dispatchTouchEvent
                / / note that at this time of the third parameter travels is target. The child is not null internal will target. Child. DispatchTouchEvent ()
                if (dispatchTransformedTouchEvent(ev, cancelChild,
                        target.child, target.pointerIdBits)) {
                    handled = true;
                }
                // If the child event is cancelled
                // Then mFirstTouchTarget = target.next
                CancelChild causes mFirstTouchTarget to be null
                if (cancelChild) {
                    if (predecessor == null) {
                        mFirstTouchTarget = next;
                    } else {
                        predecessor.next = next;
                    }
                    / / recycling target
                    target.recycle();
                    target = next;
                    continue; } } predecessor = target; target = next; }}return handled;       
}

public boolean onInterceptTouchEvent(MotionEvent ev) {...return false;
}
    
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {...if (child == null) {
         // Call view's dispatchTouchEvent
         handled = super.dispatchTouchEvent(transformedEvent);
     else{...// Call the dispatchTouchEvent of the child view
         handled = child.dispatchTouchEvent(transformedEvent);
     }
     return handled;
 }
 
 //View.java
 
 / / the View dispatchTouchEvent
 public boolean dispatchTouchEvent(MotionEvent event) {...// First check if onTouchListener is set. If it is set and true is returned, onTouchEvent will not be executed
     OnTouchListener has a higher priority than onTouchEvent
     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; }...return result;
 }
     
 public boolean onTouchEvent(MotionEvent event) {...Setting onClickListener also causes clickable to be true
     final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
     
     if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
      	 switch(action) {
                 case MotionEvent.ACTION_UP:
                     ...
                     // Callback the onClick method of OnClickListenerperformClickInternal(); .break;
         }
         // If the view is clickable, it returns true
         return true;
     }
     // Return false if view is not clickable
     return false;
 }
Copy the code

From the source code analysis above, we can summarize the basic flow:

  • First ACTION_DOWN event:

    • Initially we will clear all previous touch states because ACTION_DOWN is the beginning of a series of events

    • We then ask our onInterceptTouchEvent method whether it intercepts events by default

    • If you do not intercept the backward traversal of children, let each child view call its own dispatchTouchEvent method

    • The sub-view processing process is divided into:

      • The child view consumer events Then we will assign mFirstTouchTarget object And will alreadyDispatchedToNewTouchTarget set to true then execute the following code block:

        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {handled = true; }

        Then return handled;

      • Child view no consumer events or viewGroup intercept it himself So mFirstTouchTarget directly in dispatchTransformedTouchEvent object is empty () and call his own onTouchEvent method inside, Then return handled;

  • Post-order events (ACTION_MOVE as an example) :

    • The re-entry was divided into two situations:

      • If mFirstTouchTarget is not null (indicating that a subview has consumed the event), we use the onInterceptTouchEvent method of the viewGroup to ask the viewGroup if it intercepts the event

        if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {handled = true; }

        Again, indirectly calling child’s dispatchTouchEvent method

      • If mFirstTouchTarget is empty, directly in dispatchTransformedTouchEvent () and call them with the viewGroup own onTouchEvent method, and later his son view will not receive any other event series.

Of course, we can also get a few summaries:

  1. OnTouchListener, onClickListener, onTouchEvent priority:

    onTouchListener–>onTouchEvent–>onClickListener

  2. After the event is consumed by the child view, the viewGroup can intercept the event again using the onInterceptTouchEvent method. If intercepted, we remove the mFirstTouchTarget header

  3. Child view if you don’t want to let father view we can intercept events. Through the parent requestDisallowInterceptTouchEvent (true) to prevent the parent view intercept events, but we need when down the consumption Or subsequent events are not coming.

  4. DispatchTouchEvent distribution is the core of the event distribution mechanism. OnInterceptTouchEvent and onTouchEvent are called inside its method. Generally, we do not rewrite dispatchTouchEvent method. Instead, rewrite the onInterceptTouchEvent and onTouchEvent methods to customize touch events. The main reason for this is that the dispatchTouchEvent distribution process has been improved, and bugs are likely to occur if it is not modified properly

  5. MFirstTouchTarget is important in the overall touch event flow. MFirstTouchTarget is a TouchTarget object, which is essentially a linked list. When an event is consumed, use mFirstTouchTarget to find the event view chain directly. But notice that if the child view consumes the event, but the parent view blocks the event in some sliding scenario, MFirstTouchTarget’s head is reclaimed, mFirstTouchTarget=next is reassigned, mFirstTouchTarget is null when swiping, and ACTION_CANCEL of the child view is triggered. In the future, this event series will no longer be given to child views. The key codes are as follows:

    final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
    // Continue calling the child's dispatchTouchEvent
    / / note that at this time of the third parameter travels is target. The child is not null internal will target. Child. DispatchTouchEvent ()
    if (dispatchTransformedTouchEvent(ev, cancelChild,
            target.child, target.pointerIdBits)) {
        handled = true;
    }
    // If the child event is cancelled
    // Then mFirstTouchTarget = target.next
    CancelChild causes mFirstTouchTarget to be null
    if (cancelChild) {
        if (predecessor == null) {
            mFirstTouchTarget = next;
        } else {
            predecessor.next = next;
        }
        / / recycling target
        target.recycle();
        target = next;
        continue;
    }

Copy the code

The last

We have basically finished sorting out the series of one-touch events above, and then we will actually solve the sliding conflict scenario by understanding the event distribution mechanism, and understand its essence. Of course, if you think this article is good, please give a thumbs up, thank you~~~~