This is the third day of my participation in the November Gwen Challenge. Check out the details: the last Gwen Challenge 2021

The View event is passed from Activity–>Window–>View.

public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean consume = false;
        //1
        if (onInterceptTouchEvent(ev)) {
            //2
            consume = onTouchEvent(ev);
        } else {
            //3
            consume = child.dispatchTouchEvent(ev);
        }
        return consume;
    }
Copy the code

This is all pseudo-code for event distribution,

  • Comment 1 whether the onInterceptTouchEvent method of your ViewGroup needs to be intercepted
  • Interception event call Comment 2 This View itself consumes the call method onTouchEvent method
  • Comment 1 does not intercept the dispatchTouchEvent method that calls comment 3’s child View

Then we continue the Activity’s dispatchTouchEvent method with the following code:

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        //1
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        //2
        return onTouchEvent(ev);
    }
Copy the code
  • Note 1 shows that the Activity passes the event to the Window’s superDispatchTouchEvent method and lets the Window handle the event, as you can see if the Window does not consume the event
  • Comment 2 The Activity’s onTouchEvent method. Let’s move on to how Window is implemented, Window is an abstract class, and the concrete implementer is PhoneWindow, right

The source code is as follows.

//1
private DecorView mDecor;
public boolean superDispatchTouchEvent(MotionEvent event) {
        //2
        return mDecor.superDispatchTouchEvent(event);
    }
Copy the code
  • Note 1 mDecor is a reference to the DecorView class, which inherits FrameLayout
  • Note 2 Call the superDispatchTouchEvent method of the ViewGroup

SuperDispatchTouchEvent (ViewGroup dispatchTouchEvent);

public boolean dispatchTouchEvent(MotionEvent ev) { //1 if (actionMasked == MotionEvent.ACTION_DOWN) { cancelAndClearTouchTargets(ev); resetTouchState(); } final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! = null) {//2 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! = 0; //3 if (! disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); } else { intercepted = false; } } else { intercepted = true; } return handled; }Copy the code
  • Note 1 is initialized when the event is ACTION_DOWN (mFirstTouchTarget, mGroupFlags reset). The event starts with ACTION_DOWN and ends with ACTION_UP. This is done so that each event is not affected by previous Settings
  • The mFirstTouchTarget (more on that later) in comment 2 indicates whether the child handles the event and is not null if it does. If the ViewGroup intercepts events, the onInterceptTouchEvent method is not called in ACTION_MOVE and ACTION_UP states and intercept is true, the ViewGroup consumes these events
  • Note 3 child View through the means of ViewGroup requestDisallowInterceptTouchEvent told the parent container don’t intercept events, let the child View. The dispatchTouchEvent method code is as follows:
@Override public boolean dispatchTouchEvent(MotionEvent ev) { //1 for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); //2 if (! child.canReceivePointerEvents() || ! isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } newTouchTarget = getTouchTarget(child); if (newTouchTarget ! = null) { newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); //3 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { //4 newTouchTarget = addTouchTarget(child, idBitsToAssign); break; } } return handled; }Copy the code
  • The ViewGroup child elements are traversed backwards at comment 1, with the top layer traversed first
  • Comment 2 determines whether the child View receives events and whether the touch point is on the child View
  • Note 3 calls the dispatchTransformedTouchEvent method, if the return value is true, said the son View consumer events
  • The addTouchTarget method is called in comment 4
    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        //1
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        //2
        target.next = mFirstTouchTarget;
        //3
        mFirstTouchTarget = target;
        return target;
    }
Copy the code

Wrap the View that needs to consume the event into a single linked list structure of the TouchTarget class.

  • Take the TouchTarget object from the cache pool at comment 1
  • The Target object of the TouchTarget class in comment 2 is the header pointer, and target’s next points to the mFirstTouchTarget reference
  • The target object assigned to mFirstTouchTarget in comment 3 becomes the single-linked list header

MFirstTouchTarget: single-linked list of child views that need to consume the event in this round (ACTION_DOWN to ACTION_UP), also the single-linked list header. The last part of the ViewGroup dispatchTouchEvent method is as follows:

public boolean dispatchTouchEvent(MotionEvent ev) { //1 if (mFirstTouchTarget == null) { handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { TouchTarget target = mFirstTouchTarget; //2 while (target ! = null) { final TouchTarget next = target.next; //3 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { //4 if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } } predecessor = target; target = next; } } return handled; }Copy the code

Determine who should handle the event based on the mFirstTouchTarget header pointer.

  • There is no child View at comment 1 to handle the event, so it is handled by itself
  • The while loop in comment 2 iterates through a single linked list
  • Comment 3 determines whether the event has been consumed
  • Comment 4 on the dispatchTouchEvent method that events are sent to child views

Then we continue to analyze the mysterious method dispatchTransformedTouchEvent source code is as follows:

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        if (child == null) {
            //1
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }
            //2
            handled = child.dispatchTouchEvent(transformedEvent);
        }
        return handled;
Copy the code
  • If a ViewGroup has no children, the parent View’s dispatchTouchEvent method is called in comment 1 to handle the event itself
  • The dispatchTouchEvent method of the child element is called at comment 2 to implement event distribution

View dispatchTouchEvent method

public boolean dispatchTouchEvent(MotionEvent event) { boolean result = false; if (onFilterTouchEventForSecurity(event)) { ListenerInfo li = mListenerInfo; //1 if (li ! = null && li.mOnTouchListener ! = null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } //2 if (! result && onTouchEvent(event)) { result = true; } } return result; }Copy the code
  • In comment 1, it is judged that if mOnTouchListener is not empty, the onTouch method will be called, and the onTouchEvent method will not be called, indicating that the onTouch method has a higher priority
  • The onTouchEvent method is called at comment 2

Continue to analyze the onTouchEvent method source:

public boolean onTouchEvent(MotionEvent event) { 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: if (! focusTaken) { if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (! post(mPerformClick)) { performClickInternal(); } } break; } return true; } return false; }Copy the code

Whenever the View is in CLICKABLE and LONG_CLICKABLE state, return true to indicate consumption. Next, let’s look at the click action, which looks like this:

public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li ! = null && li.mOnClickListener ! = null) { playSoundEffect(SoundEffectConstants.CLICK); //1 li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); notifyEnterOrExitForAutoFillIfNeeded(true); return result; }Copy the code

As we can see from comment 1, if the onClick callback is set, the onTouchEvent method takes precedence over the onClick method.

These are all analysis View event distribution mechanisms

Public id: Programmer Cat