The outline

  • Event What events are distributed and what methods are involved
  • How is event distribution passed from an Activity to a View
  • View event distribution
  • ViewGroup event distribution

1. Events What events are distributed and what methods are involved

  • When the user touches the screen, an event is generated, as in AndroidMotionEventThere are many static constants in this class:
The name of the value meaning
ACTION_DOWN 0 The pressed gesture has started and the action contains the initial start position.
ACTION_UP 1 After the gesture is pressed, the movement contains the final release position and all intermediate points since the last drop or move event
ACTION_MOVE 2 A change has happened during a press gesture (between {@link #ACTION_DOWN} and {@link #ACTION_UP})
ACTION_CANCEL 3 The current gesture is aborted.
. . .
  • By default, event distribution is distributed in the order from Activity to ViewGroup to View
  • DecorView(mDecor is the View returned by getWindow().getDecorView(), and the View set by setContentView is a child of that View). Pass it to the Activity’s View tree.

1. Important methods of View event distribution:diapatchTouchEventandtouchEvent.onClick

|---View
|   |---boolean dispatchTouchEvent(MotionEvent event)
|   |--- boolean onTouchEvent(MotionEvent event)
Copy the code

1.1 dispatchTouchEvent: True if the event is 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)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            // Check whether there is an event listEnter in listenerInfo and monTouchListener. onTouc returns true
            ListenerInfo li = mListenerInfo;
            if(li ! =null&& li.mOnTouchListener ! =null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
						// Second important point, if the above judgment is false and onTouchEventr returns true, consume the event
            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;
    }

Copy the code
  1. In the dispatchTouchEventfinal int actionMasked = event.getActionMasked();
  2. ListenerInfo:viewAll listener information is encapsulated in an object.
  3. There are two judgments in this method:
 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;
            }
Copy the code

If onTouchEvent or onTouchListenner is true, that event will be consumed, and the onTouchEvent will be done here

  • boolean onTouchEvent
  • MotionEvent.ACTION_UP:Call thePerformClick()

Three questions:

1. Called in the activity

 mTouchView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.e("TAG"."onTouch: " + event.getAction());
                return false; }}); mTouchView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("TAG"."onClick: "); }});/***********TabLayout************/   
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("TAG"."onTouchEvent: "+event.getAction());
        return super.onTouchEvent(event);
    }
Copy the code

Order of execution:

2. What if Tablyout returns ture directly?

Do not execute onClick because onClick is in ACTION_UP in view.onTouchEvent. If you return true, you will not use the method in super.

3. What if dispatchEvent return true?

Nothing

Distribution process

super.dispatchEvent->ListenerInfo->super.onTouchEvent(event)->ACTION_UP->performListener().
Copy the code

2. ViewGroup event distribution:

The first is actionDown:

 if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) { //
                final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
                if(! disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action);// restore action in case it was changed
                } else {
                    intercepted = false; }}Copy the code
  • MFirstTouchTarget: This is important, disallowIntercept is only called when pressed for the first time, this isa pit if you want to change the effect later.
|---ViewGroup
|   |---dispatchTouchEvent
|   |   |---onInterceptTouchEvent(ev)
Copy the code

There are cases where the View consumes time (like click onTouchListener)

  • Press for the first time to come in (down) – > ViewGroup. DispatchTouchEvent – > ViewGroup. OnInterceptTouchEvent (ev) — – > the dispatchEvent – (the view that a)
  • In a second time (move) ViewGroup. DispatchTouchEvent – > ViewGroup. OnInterceptTouchEvent (ev) — – > the dispatchEvent – (the view that a)
  • (up) for the third time) ViewGroup. DispatchTouchEvent – > ViewGroup. OnInterceptTouchEvent (ev) — – > the dispatchEvent – (the view that a) — — > the onC lick

There is no consumption event in the child View. Only walk again

  • Come in for the first time (down) ViewGroup. DispatchTouchEvent – > ViewGroup. OnInterceptTouchEvent (ev) — – > the dispatchEvent – view. The onTouch – > onT ouch Event

ViewGroup source code analysis

actionDown

 /** * Clears all touch targets. */
    private void clearTouchTargets(a) {
        TouchTarget target = mFirstTouchTarget;
        if(target ! =null) {
            do {
                TouchTarget next = target.next;
                target.recycle();
                target = next;
            } while(target ! =null);
            mFirstTouchTarget = null; / / remove target
        }
    }
<!--------------------------------------------------->
      if(! canceled && ! intercepted)// intercepted defaults to no interception
      {
         if (newTouchTarget == null&& childrenCount ! =0) {
              for (int i = childrenCount - 1; i >= 0; i--) {// The antisequence for loop takes the top layout first
                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                }
              }
         }
      }
     
      
       
Copy the code


/** * Transforms a motion event into the coordinate space of a particular child view, * filters out irrelevant pointer ids, and overrides its action if necessary. * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. */
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        // Calculate the number of pointers to deliver.
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        // If for some reason we ended up in an inconsistent state where it looks like we
        // might produce a motion event with no pointers in it, then drop the event.
        if (newPointerIdBits == 0) {
            return false;
        }
        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }

        // Perform any necessary transformations and dispatch.
        if (child == null) {
            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());
            }

            handled = child.dispatchTouchEvent(transformedEvent);
        }

        // Done.
        transformedEvent.recycle();
        return handled;
    }

Copy the code