The article directories

  • preface
    • One touch event
    • Event handling involves important methods
    • Why is event distribution needed
    • The Demonstrate code for this article
  • Source code analysis
    • Knowledge of bedding
  • ACTION_DOWN processing in ViewGroup
    • ACTION_DOWN
    • The child view consumes ACTION_DOWN
    • Child views do not consume ACTION_DOWN
  • ACTION_MOVE processing of ViewGroup
    • Suppose the child view consumes the previous ACTION_DOWN event
      • Second ACTION_MOVE processing after viewGroup interception
    • Assume that the subview did not consume the previous ACTION_DOWN event
  • ACTION_UP processing under ViewGroup
    • The child view consumes ACTION_DOWN
    • The child view didn’t consume ACTION_DOWN
  • The view of ACTION_DOWN
  • The view of ACTION_MOVE
  • The view of ACTION_UP
  • conclusion

preface

This article is based on a further understanding of the Art of Android Development, partly excerpted from the book. Why write this article? The <

> content is very well summarized but many details are not posted for further reading.

One touch event

One touch event: refers to from aACTION_DOWN, and multipleACTION_MOVE, and individualACTION_UP/ACTION_CANCELComposition.



As for theACTION_UPandACTION_CANCELThe difference will be explained later in the source code analysis.

Event handling involves important methods

Activity: ————–dispatchTouchEvent ————–onTouchEvent

ViewGroup

————–dispatchTouchEvent

————–onTouchEvent

————–onInterceptTouchEvent

View

————–dispatchTouchEvent

————–onTouchEvent

In addition, only viewgroups can distribute events, and views can choose to consume or not consume events.

Why is event distribution needed

There is only one touch event, but it is possible that multiple views superimposed on the View need to be processed at the same time. In this case, according to the relevant business logic, which View should be processed.

The figure above has two superimposed listViews. Suppose we scroll down the child ListView. Should we slide the parent view or the child View?

The Demonstrate code for this article

//MainActivity.java
class MainActivity : Activity(a){
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
        log("dispatchTouchEvent ${MotionEvent.actionToString(ev.action)}");
        return super.dispatchTouchEvent(ev)
    }

    override fun onTouchEvent(ev: MotionEvent): Boolean {
        log("onTouchEvent ${MotionEvent.actionToString(ev.action)}");
        return super.onTouchEvent(ev)
    }

    fun log(msg: String) {
        Log.e("MainActivity"."${msg} ")}}Copy the code
//MyViewGroup.java

class MyViewGroup @JvmOverloads constructor(
    context: Context.attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {

        log("dispatchTouchEvent ${MotionEvent.actionToString(ev.action)}")

        return super.dispatchTouchEvent(ev)
    }

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        log("onInterceptTouchEvent ${MotionEvent.actionToString(ev.action)}")
        return super.onInterceptTouchEvent(ev)
    }

    override fun onTouchEvent(ev: MotionEvent): Boolean {
        log("onTouchEvent ${MotionEvent.actionToString(ev.action)}")
        return super.onTouchEvent(ev)
    }

    fun log(msg: String) {
        Log.e("MyViewGroup"."${msg} ")}}Copy the code
//MyView.java
class MyView : View {
    constructor(context: Context?) : super(context) {} constructor(context: Context? , attrs: AttributeSet?) :super(context, attrs) {} constructor(context: Context? , attrs: AttributeSet? , defStyleAttr: Int) :super(
        context,
        attrs,
        defStyleAttr
    ) {
    }

    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
        log("dispatchTouchEvent ${MotionEvent.actionToString(ev.action)}")
        return super.dispatchTouchEvent(ev)
    }

    override fun onTouchEvent(ev: MotionEvent): Boolean {
        log("onTouchEvent ${MotionEvent.actionToString(ev.action)}")
        return super.onTouchEvent(ev)
    }

    fun log(msg: String) {
        Log.e("MyView"."${msg} ")}}Copy the code
<! --activity_main.xml-->


      
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <com.example.example.MyViewGroup
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="#f0f"
        android:text="Trigger network request"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <com.example.example.MyView
            android:layout_gravity="center"
            android:layout_width="150dp"
            android:layout_height="150dp"
            android:background="#f00" />

    </com.example.example.MyViewGroup>

</androidx.constraintlayout.widget.ConstraintLayout>
Copy the code

Preview:

Source code analysis

Knowledge of bedding

This summary is intended only as a prelude to source code analysis.

All the click event will be from the Activity. The dispatchTouchEvent trigger. Click on the MyView output of the Demonstrate as shown above:

MainActivity: dispatchTouchEvent ACTION_DOWN --------- Starting point of ACTION_DOWN event distribution MyViewGroup: dispatchTouchEvent ACTION_DOWN MyViewGroup: onInterceptTouchEvent ACTION_DOWN MyView: dispatchTouchEvent ACTION_DOWN MyView: onTouchEvent ACTION_DOWN MyViewGroup: onTouchEvent ACTION_DOWN MainActivity: OnTouchEvent ACTION_DOWN MainActivity: dispatchTouchEvent ACTION_UP --------- Starting point of ACTION_UP event distribution MainActivity: onTouchEvent ACTION_UPCopy the code

We continue to read the source code:

class Activity{
	public boolean dispatchTouchEvent(MotionEvent ev) {
			ACTION_DOWN calls onUserInteraction
			OnUserInteraction is null by default. You can implement such operations as burying points in this function
	        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
	            onUserInteraction();
	        }
	        / / return to PhoneWindow getWindow
	        if (getWindow().superDispatchTouchEvent(ev)) {
	            return true;
	        }
	        // If PhoneWindow does not consume, call the Activity's onTouchEvent function
	        // This conclusion can be put down
	        return onTouchEvent(ev);
	}
	
	// An empty implementation
	public void onUserInteraction(a) {}}Copy the code

The above we can get several conclusions: conclusion 1: events must be from the Activity. The dispatchTouchEvent began handing out. (here is not out of this conclusion, but for the sake of completeness.)

Conclusion 2: ACTION_DOWN event must call Activity. OnUserInteraction.

Conclusion 3: PhoneWindow will call back to activity. onTouchEvent(action_down. ACTION_MOVE,ACTION_UP) without consuming touch events.

Call PhoneWindow’s superDispatchTouchEvent function and determine whether to call activity.onTouchEvent or return true based on the return value

//PhoneWindow.java
class PhoneWindow{
	private DecorView mDecor;

	@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
    	// The source code is traced to the DecorView
        returnmDecor.superDispatchTouchEvent(event); }}Copy the code
//DecorView.java
class DecorView extends FrameLayout{
	public boolean superDispatchTouchEvent(MotionEvent event) {
		// FrameLayout inherits the ViewGroup dispatchTouchEvent function (FrameLayout inherits the ViewGroup and does not override this function)
        return super.dispatchTouchEvent(event); }}Copy the code

And so the event schedule becomesActivityCalls to theViewGroup.dispatchTouchEventThe process of a function is not analyzed laterActivityPass the event toDecorView“, but simple understandingActivityPass the event to us aboveMyViewGroup(because ofDecorViewIs also aViewGroup, our custom classMyViewGroupThe same type, which simplifies our analysis process).

simplified

ACTION_DOWN processing in ViewGroup

This summary only analyzes the ACTION_DOWN processing of ViewGroup, which is also difficult

ACTION_DOWN

At this point we assume that we click on the MyView of the previous example and an ACTION_DOWN event is generated.

class Activity{
	public boolean dispatchTouchEvent(MotionEvent ev) {
			// Generate ACTION_DOWN to call onUserInteraction
	        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
	            onUserInteraction();
	        }
	        // Here we assume that MyViewGroup's dispatchTouchEvent function is called directly
	        if (getWindow().superDispatchTouchEvent(ev)) {
	            return true;
	        }
	        
	        // If MyViewGroup does not consume ACTION_DOWN, call the Activity's onTouchEvent function
	        return onTouchEvent(ev);
	}
	
	// An empty implementation
	public void onUserInteraction(a) {}}Copy the code

MyViewGroup inherits ViewGroup, so let’s just look at dispatchTouchEvent for ViewGroup

//ViewGroup.java
class ViewGroup{
	 public boolean dispatchTouchEvent(MotionEvent ev) {
      

        boolean handled = false;
        / / some security checks, here we consider onFilterTouchEventForSecurity returns true
        if (onFilterTouchEventForSecurity(ev)) {
            // Currently returns ACTION_DOWN
            final int action = ev.getAction();
            
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            ACTION_DOWN resets the flag bit and cancels the previous sequence of events
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // The ACTION_DOWN event cancels the previous event and sends an ACTION_CANCEL event to the previous view that was processing the event
                cancelAndClearTouchTargets(ev);
                // Reset some flag bits
                resetTouchState();
            }
            / /... slightly
        }

      
        returnhandled; }}Copy the code

We don’t need too care about cancelAndClearTouchTargets function, but we need to look at the resetTouchState

//ViewGroup.java
class ViewGroup{
  protected int mGroupFlags;
  private TouchTarget mFirstTouchTarget;

  private void resetTouchState(a) {
  		// Set mFirstTouchTarget to null
        clearTouchTargets();
        // FLAG_DISALLOW_INTERCEPT is cancelled
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
  }
  private void clearTouchTargets(a) {
        TouchTarget target = mFirstTouchTarget;
        if(target ! =null) {
           / /.. slightly
            mFirstTouchTarget = null; }}}Copy the code

The flag bit above and the mFirstTouchTarget are used and we’ll talk about that later, and we’ll move on to code analysis

class ViewGroup{
	 public boolean dispatchTouchEvent(MotionEvent ev) {
      
        boolean handled = false;
        / / some security checks, here we consider onFilterTouchEventForSecurity returns true
        if (onFilterTouchEventForSecurity(ev)) {
            // Currently returns ACTION_DOWN
            final int action = ev.getAction();

            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Reset mFirstTouchTarget to null
            // Unflag mGroupFlags FLAG_DISALLOW_INTERCEPT
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // 
            final boolean intercepted;
            ActionMasked == motionEvent. ACTION_DOWN is true
            //mFirstTouchTarget ! = null to false
            if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
                // The resetTouchState function disables FLAG_DISALLOW_INTERCEPT so it must be false
                final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
                OnInterceptTouchEvent = onInterceptTouchEvent = onInterceptTouchEvent
                if(! disallowIntercept) {//onInterceptTouchEvent may change the ACTION of ev, but there is no need to pay too much attention to this alternative case
                    intercepted = onInterceptTouchEvent(ev);
                    // Prevent ev from being modified
                    ev.setAction(action); 
                } else {
                    intercepted = false; }}else {
                intercepted = true;
            }

            / / a little
        }

        

        returnhandled; }}Copy the code

ACTION_DOWN is not affected by FLAG_DISALLOW_INTERCEPT and will be called onInterceptTouchEvent. FLAG_DISALLOW_INTERCEPT may affect whether onInterceptTouchEvent is called if the mFirstTouchTarget is not null, whereas the mFirstTouchTarget is not null only in ACTION_MOVE/ACTION _UP.

MFirstTouchTarget is a view object that handles a sequence of touch events. Another question is how do YOU change the FLAG_DISALLOW_INTERCEPT bit? We can look at the following source code

//ViewParent.java
public interface ViewParent {
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept);
}
//ViewGroup.java
class ViewGroup implement ViewParent{
  public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
		// Return if it has been set
        if(disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0)) {
            return;
        }
		// Set the flag bit
        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass through to the parent view of the ViewGroup
        // This will be passed up one layer after another
        if(mParent ! =null) { mParent.requestDisallowInterceptTouchEvent(disallowIntercept); }}}Copy the code

Note here that flag bits are passed up one layer at a time.

Moving on to the dispatchTouchEvent function,onInterceptTouchEvent is null by default and returns false.

//ViewGroup.java
class ViewGroup{
	  public boolean dispatchTouchEvent(MotionEvent ev) {
      



        boolean handled = false;
        / / some security checks, here we consider onFilterTouchEventForSecurity returns true
        if (onFilterTouchEventForSecurity(ev)) {
            // Currently returns ACTION_DOWN
            final int action = ev.getAction();

            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Reset mFirstTouchTarget to null
            // Unflag mGroupFlags FLAG_DISALLOW_INTERCEPT
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // 
            final boolean intercepted;
            ActionMasked == motionEvent. ACTION_DOWN is true
            //mFirstTouchTarget ! = null to false
            if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
                 // The resetTouchState function disables FLAG_DISALLOW_INTERCEPT so it must be false
                final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
                if(! disallowIntercept) {// An empty implementation that returns false by default
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); 
                } else {
                    intercepted = false; }}else {
               //ACTION_DOWN will not be executed here
                intercepted = true;
            }
            ActionMasked == MotionEvent.ACTION_CANCEL is false
            //resetCancelNextUpFlag returns false, we don't need to care,
            Canceled overall is false
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            // Update list of touch targets for pointer down, if needed.
            //true
            final booleansplit = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) ! =0;

            TouchTarget newTouchTarget = null;

            boolean alreadyDispatchedToNewTouchTarget = false;
            //intercepted returns false! Intercepted to true
            Canceled is false
            if(! canceled && ! intercepted) {ActionMasked is true because it is ACTION_DOWN
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {

                    // The object ACTION_DOWN event is 0 in this case, and getActionIndex returns the index of the finger touched
                    final int actionIndex = ev.getActionIndex(); 

				
                   final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                    // The number of child views
                    final int childrenCount = mChildrenCount;
                    // If the number of sub-views is not zero, select a view to determine whether event distribution can be performed
                    if (newTouchTarget == null&& childrenCount ! =0) {

                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // Sort the view by the order in which it was drawn and the z size of the child view. The z priority of the child view is greater than the order in which it was drawn
                        // The larger the z value of the subview is, the higher it is after the preorderedList. If there is no z value, the subview is sorted according to the order in which it was drawn in the viewGroup
                        // The higher the priority of drawing after the ViewGroup, the higher the priority of drawing after the preorderedList
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
						
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                    
                    }

                   / / a little}}/ / a little
        }

       
        returnhandled; }}Copy the code

We see buildTouchDispatchChildList correlation function implementation

//ViewGroup.java
class ViewGroup{

	 public ArrayList<View> buildTouchDispatchChildList(a) {
	        return buildOrderedChildList();
	 }

	ArrayList<View> buildOrderedChildList(a) {
		
	        final int childrenCount = mChildrenCount;
	        // The children array of the ViewGroup is added to the array according to the order in which the children array is drawn
	        // There is only one child view, or all child views have no z-axis attributes, just return empty objects
	        // The children array will be used directly.
	        //childrenCount <= 1
	        / /! HasChildWithZ () can be understood as: if all child views have no z-axis, it can be retrieved in reverse order of the children array priority
	        if (childrenCount <= 1| |! hasChildWithZ())return null;
	
	        if (mPreSortedChildren == null) {
	            mPreSortedChildren = new ArrayList<>(childrenCount);
	        } else {
	            mPreSortedChildren.clear();
	            mPreSortedChildren.ensureCapacity(childrenCount);
	        }
	
	        // Get a priority view set object based on the drawing order and the Z-axis. The algorithm has to be stable
	        final boolean customOrder = isChildrenDrawingOrderEnabled();
	        for (int i = 0; i < childrenCount; i++) {
	            // add next child (in child order) to end of list
	            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
	            final View nextChild = mChildren[childIndex];
	            final float currentZ = nextChild.getZ();
	
	            
	            int insertIndex = i;
	            while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
	                insertIndex--;
	            }
	            mPreSortedChildren.add(insertIndex, nextChild);
	        }
	        returnmPreSortedChildren; }}Copy the code

One important thing we can learn from this is that the distribution order priority of the ViewGroup also needs to consider the z-axis of the child view.

Let’s dig deeper into the source code

//ViewGroup.java
class ViewGroup{
	    public boolean dispatchTouchEvent(MotionEvent ev) {
			/ /... slightly
			//intercepted returns false! Intercepted to true
            Canceled is false
            if(! canceled && ! intercepted) {ActionMasked is true because it is ACTION_DOWN
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {

                    // The object ACTION_DOWN event is 0 in this case, and getActionIndex returns the index of the finger touched
                    final int actionIndex = ev.getActionIndex();


                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                    // The number of child views
                    final int childrenCount = mChildrenCount;
                    // If the number of sub-views is not zero, select a view to determine whether event distribution can be performed
                    if (newTouchTarget == null&& childrenCount ! =0) {

                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // Sort the view by the order in which it was drawn and the z size of the child view. The z priority of the child view is greater than the order in which it was drawn
                        // The larger the z value of the subview is, the higher it is after the preorderedList. If there is no z value, the subview is sorted according to the order in which it was drawn in the viewGroup
                        // The higher the priority of drawing after the ViewGroup, the higher the priority of drawing after the preorderedList
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();

                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();

                        final View[] children = mChildren;

                        // Get the view in reverse order and determine which view can be processed
                        for (int i = childrenCount - 1; i >= 0; i--) {

                            // get the subscript
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            // Get the child view
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                            / / canReceivePointerEvents () returns true if the current view visible or executing animation
                            / / isTransformedTouchPointInView judge whether the coordinates of the touch currently fall within the scope of the view
                            // The whole if determines whether the current view can handle the event. If the current view cannot handle the event, skip this loop and find the next view
                            if(! child.canReceivePointerEvents() || ! isTransformedTouchPointInView(x, y, child,null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
                            ACTION_DWON event getTouchTarget must return NULL. Analysis later
                            newTouchTarget = getTouchTarget(child);
                            // Because newTouchTarget==null so if we skip
                            if(newTouchTarget ! =null) {
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            // Very important function, let's look at the concrete implementation of this function
                            // This function roughly returns a call to the View's dispatchTouchEvent
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {								
                               / / a little
                                break; }}if(preorderedList ! =null) preorderedList.clear();
                    }

                    if (newTouchTarget == null&& mFirstTouchTarget ! =null) {
                      / /... slightly}}}/ /... slightly}}Copy the code

We first see dispatchTransformedTouchEvent

//ViewGroup.java
class ViewGroup{
	private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case. We don't need to perform any transformations
        // or filtering. The important part is the action, not the contents.
        final int oldAction = event.getAction();
        // Cancel is false and oldAction is ACTION_DWON, so skip if
        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;
        }

        // There are more than one touch points returned.
        final int oldPointerIdBits = event.getPointerIdBits();
        // Check the number of old and new touch points, if not equal must be 0
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        // If the status is inconsistent, the abnormal status is returned directly
        if (newPointerIdBits == 0) {
            return false;
        }

        // If the number of pointers is the same and we don't need to perform any fancy
        // irreversible transformations, then we can reuse the motion event for this
        // dispatch as long as we are careful to revert any changes we make.
        // Otherwise we need to make a copy.
        final MotionEvent transformedEvent;
        NewPointerIdBits == oldPointerIdBitstrue
        if (newPointerIdBits == oldPointerIdBits) {
            // Do some offset calculations, the code here does not affect the analysis flow, so skip
            //hasIdentityMatrix
           // We call it fals
            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);
        }

        // Child is not empty
        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            // This calculation does not affect us because we have no scroll and offset
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }

            / / distribution to MyView. DispatchTouchEvent
            handled = child.dispatchTouchEvent(transformedEvent);
        }

        // Done.
        transformedEvent.recycle();
        returnhandled; }}Copy the code

This is going to determine the future of the ViewGroup code based on whether the subview consumes it, and we’re going to do it in two different ways.

The child view consumes ACTION_DOWN

Assume that all MyViewGroup dispatchTransformedTouchEvent returns true.

class ViewGroup{
	    public boolean dispatchTouchEvent(MotionEvent ev) {
			
			boolean handled = false;
				
            TouchTarget newTouchTarget = null;

            // Get the view in reverse order and determine which view can be processed
             for (int i = childrenCount - 1; i >= 0; i--) {

                 // get the subscript
                 final int childIndex = getAndVerifyPreorderedIndex(
                         childrenCount, i, customOrder);
                 // Get the child view
                 final View child = getAndVerifyPreorderedView(
                         preorderedList, children, childIndex);

                 / / canReceivePointerEvents () returns true if the current view visible or executing animation
                 / / isTransformedTouchPointInView judge whether the coordinates of the touch currently fall within the scope of the view
                 // The whole if determines whether the current view can handle the event. If the current view cannot handle the event, skip this loop and find the next view
                 if(! child.canReceivePointerEvents() || ! isTransformedTouchPointInView(x, y, child,null)) {
                     ev.setTargetAccessibilityFocus(false);
                     continue;
                 }
                 ACTION_DWON event getTouchTarget must return NULL. Analysis later
                 newTouchTarget = getTouchTarget(child);
                 // Because newTouchTarget==null so if we skip
                 if(newTouchTarget ! =null) {
                     newTouchTarget.pointerIdBits |= idBitsToAssign;
                     break;
                 }

                 resetCancelNextUpFlag(child);
                 // Assume that the function returns true
                 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                 	// It's important to assign the value to newTouchTarget, which contains a reference to the child View
                 	// The inner mFirstTouchTarget will be assigned to view
                     newTouchTarget = addTouchTarget(child, idBitsToAssign);
                     // Write this down
                     alreadyDispatchedToNewTouchTarget = true;
                     // End the loop
                     break; }}}/ / after the break... Because newTouchTarget is not null, the concrete method body is skipped
         if (newTouchTarget == null&& mFirstTouchTarget ! =null) {
            / / a little
         }
			// mFirstTouchTarget is not null
            if (mFirstTouchTarget == null) {
               // There is no comparison here
            } else {
              
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while(target ! =null) {
                    final TouchTarget next = target.next;
                    / / alreadyDispatchedToNewTouchTarget to true
                    //target == newTouchTarget is true
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                       / / a little
                    }
                    // exit the loop after the assignmentpredecessor = target; target = next; }}//END dispatchTouchEvent
		return handled;
	}
	
 	// Note that mFirstTouchTarget is assigned
 	private TouchTarget addTouchTarget(View child, int pointerIdBits) {
        TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        returntarget; }}Copy the code

So there’s a conclusion subview consumption event, and mFirstTouchTarget is going to be assigned to the corresponding subview.

Child views do not consume ACTION_DOWN

class ViewGroup{
	  public boolean dispatchTouchEvent(MotionEvent ev) {


        boolean handled = false;
        / / some security checks, here we consider onFilterTouchEventForSecurity returns true
        if (onFilterTouchEventForSecurity(ev)) {

            / / a little

            TouchTarget newTouchTarget = null;

            boolean alreadyDispatchedToNewTouchTarget = false;
            //intercepted returns false! Intercepted to true
            Canceled is false
            if(! canceled && ! intercepted) {ActionMasked is true because it is ACTION_DOWN
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {

                    / / a little

                    // The number of child views
                    final int childrenCount = mChildrenCount;
                    // If the number of sub-views is not zero, select a view to determine whether event distribution can be performed
                    if (newTouchTarget == null&& childrenCount ! =0) {


                        // Get the view in reverse order and determine which view can be processed
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            There is no child view consumption event until the end of the loop}}// Since both mFirstTouchTarget and newTouchTarget are null, no analysis is required here
                    if (newTouchTarget == null&& mFirstTouchTarget ! =null) {
                      / /...}}}// Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                / / because there is no child view consumption, dispatchTransformedTouchEvent internal dispatchTouchEvent function will call the superclass
                // Notice that the third argument is passed null
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                / / a little
            }

            // Update list of touch targets for pointer up or cancel, if needed.
            / / a little
        }

        returnhandled; }}Copy the code

We see dispatchTransformedTouchEvent function again.

//ViewGroup.java
class ViewGroup{
	private boolean dispatchTransformedTouchEvent(MotionEvent event, 
												  boolean cancel,
            									  View child,
             									  int desiredPointerIdBits) {
            
        final boolean handled;
        // Callback to view's dispatchTouchEvent function
        if (child == null) {
        		handled = super.dispatchTouchEvent(transformedEvent);
      	}
      	returnhandled; }}Copy the code

ACTION_MOVE processing of ViewGroup

Suppose the child view consumes the previous ACTION_DOWN event


//ViewGroup.java
public class ViewGroup {
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean handled = false;
            // Currently returns ACTION_MOVE
            final int action = ev.getAction();

            final int actionMasked = action & MotionEvent.ACTION_MASK;



            //
            final boolean intercepted;
            ActionMasked == motionEvent. ACTION_DOWN is false
            //mFirstTouchTarget ! If null is true, mFirstTouchTarget will point to the child view after consuming the DWON event
            if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {

                //ACTION_MOVE does not reset the flag so the intercept event is FLAG_DISALLOW_INTERCEPT
                final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
                if(! disallowIntercept) {// An empty implementation that returns false by default
                    intercepted = onInterceptTouchEvent(ev);
                } else {
                    intercepted = false; }}else {
                //ACTION_DOWN will not be executed here
                intercepted = true;
            }



            ActionMasked == MotionEvent.ACTION_CANCEL is false
            //resetCancelNextUpFlag returns false, we don't need to care,
            Canceled overall is false
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            //true
            final booleansplit = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) ! =0;

            TouchTarget newTouchTarget = null;

            boolean alreadyDispatchedToNewTouchTarget = false;

           

            // mFirstTouchTarget is not null
            if (mFirstTouchTarget == null) {
              / / a little
            } else {
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                / / target isn't empty
                while(target ! =null) {
                    final TouchTarget next = target.next;

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

                        // There are two ways to analyze the source code
                        // The ViewGroup onInterceptTouchEvent returns true,cancelChild is true
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        / / if cancelChild to true. DispatchTransformedTouchEvent will be passed to the child view a cancel events
                        // If cancelChild is false, the current event is passed to target.child

                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        // This is true for intercepting events
                        if (cancelChild) {
                            // Must be null
                            if (predecessor == null) {
                                // Reset mFirstTouchTarget object
                                mFirstTouchTarget = next;
                            } else {
                                / / a little
                            }
                            target.recycle();
                            target = next;
                            continue; }}/ / a little}}Canceled true resets all states
            if (canceled
                    || actionMasked == MotionEvent.ACTION_UP
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                resetTouchState();
            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
              / / a little
            }

        returnhandled; }}Copy the code

/ / below for dispatchTransformedTouchEvent cancel to true source

//ViewGroup.java
class ViewGroup{
	private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                                                  View child, int desiredPointerIdBits) {
        final boolean handled;


        final int oldAction = event.getAction();
        // Since cancel is true, the loop is entered
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                // Call ViewGroup's dispatchTouchEvent function to handle ACTION_CANCEL events
                handled = super.dispatchTouchEvent(event);
            } else {
                // Pass an ACTION_CANCEL event to the child view
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            returnhandled; }}}Copy the code

/ / below for dispatchTransformedTouchEvent cancel to false of the source code

//ViewGroup.java
class ViewGroup{
	private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                                                  View child, int desiredPointerIdBits) {
         if (child == null) 
         {
           handled = super.dispatchTouchEvent(event);
          } else {
				// Events are distributed directly to the child view
               handled = child.dispatchTouchEvent(event);

          }
         returnhandled; }}Copy the code

Conclusion 6: If the child view consumes a DOWN event and the parent viewGroup intercepts the event on MOVE, it passes a Cancel to the child view. Notice that the ViewGroup intercepts the MOVE event, but the MOVE event is not passed to the ViewGroup onTouchEvent, and subsequent events are passed to the ViewGroup. OnTouchEvent.

Second ACTION_MOVE processing after viewGroup interception

This section is based on the case where the child View consumes ACTION_DOWN and the first ACTION_MOVE is intercepted by the parent ViewGroup. Let’s look at how the parent ViewGroup handles subsequent action_moves in this case.


//ViewGroup.java
public class ViewGroup {
    public boolean dispatchTouchEvent(MotionEvent ev) {


        boolean handled = false;
        / / some security checks, here we consider onFilterTouchEventForSecurity returns true

        // Currently returns ACTION_MOVE
        final int action = ev.getAction();

        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // Reset mFirstTouchTarget to null
        // Unflag mGroupFlags FLAG_DISALLOW_INTERCEPT
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        //
        final boolean intercepted;
        ActionMasked == MotionEvent.ACTION_MOVE is false
        //mFirstTouchTarget ! = null to false
        // The overall judgment is false
        // If onInterceptTouchEvent intercepts a move event, it will not be intercepted later
        if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
           // Intercepting code does not walk
        } else {
            // Execute to this point
            intercepted = true;
        }


        ActionMasked == MotionEvent.ACTION_CANCEL is false
        //resetCancelNextUpFlag returns false, we don't need to care,
        Canceled overall is false
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        // Update list of touch targets for pointer down, if needed.
        //true
        final booleansplit = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) ! =0;

        TouchTarget newTouchTarget = null;

        boolean alreadyDispatchedToNewTouchTarget = false;


        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
            // The view dispatchTouchEvent function of its parent class will be called due to the third null, and the view. DispatchTouchEvent will call onTouchEvent
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            / /.. Since it will not be carried out here... slightly
        }
        returnhandled; }}Copy the code

Conclusion 7: After a ViewGroup intercepts the first move event, it does not call the interceptor again and calls its own descendant View’s dispatchTouchEvent. And view.dispatchTouchEvent is called back to ontouchEvent

Assume that the subview did not consume the previous ACTION_DOWN event


//ViewGroup.java
public class ViewGroup {
    public boolean dispatchTouchEvent(MotionEvent ev) {

        boolean handled = false;

        // Currently returns ACTION_MOVE
        final int action = ev.getAction();

        final int actionMasked = action & MotionEvent.ACTION_MASK;

        final boolean intercepted;
        ActionMasked == motionEvent. ACTION_DOWN is false
        / / mFirstTouchTarget is empty
        // So no interceptor is entered
        if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
          / / a little
        } else {
            // The direct flag is blocked
            intercepted = true;
        }


        ActionMasked == MotionEvent.ACTION_CANCEL is false
        //resetCancelNextUpFlag returns false, we don't need to care,
        Canceled overall is false
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

       
        TouchTarget newTouchTarget = null;

        boolean alreadyDispatchedToNewTouchTarget = false;

        //intercepted returns true! Intercepted to false
        if(! canceled && ! intercepted) {/ / a little
        }

        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
            // // Because of the third null, the view dispatchTouchEvent function of its parent class is called, and the view.dispatchTouchEvent callback onTouchEvent
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
           / / a little
        }

        / / a little
        returnhandled; }}Copy the code

Conclusion 8: If all children of a ViewGroup do not consume ACTION_DWON events, then no distribution will be performed on the judgment children and the dispatchTouchEvent of its own descendant view will be called. And view.dispatchTouchEvent is called back to ontouchEvent

Note here: Our root view is a decorView that we can’t touch, so when our child view doesn’t consume DWON, the decorView doesn’t send any events, but the decorView can receive subsequent events.

Conclusion 9: With the presence of a decorView, if the DWON event does not have any view consumption, no event is issued below. But the decorView receives subsequent events.

ACTION_UP processing under ViewGroup

The child view consumes ACTION_DOWN

The context of this section is that the child view consumes ACTION_DOWN, and the parent ViewGroup does not intercept MOVE events


//ViewGroup.java
public class ViewGroup {
    public boolean dispatchTouchEvent(MotionEvent ev) {

        boolean handled = false;

        // Currently returns ACTION_UP
        final int action = ev.getAction();

        final int actionMasked = action & MotionEvent.ACTION_MASK;

        final boolean intercepted;
        ActionMasked == motionEvent. ACTION_DOWN is false
        //mFirstTouchTarget ! =null true
        if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
            // Whether to call the intercept function according to FLAG_DISALLOW_INTERCEPT
            final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
            if(! disallowIntercept) {// An empty implementation that returns false by default
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action);
            } else {
                intercepted = false; }}else {
           // It will not be executed to this point
        }



        ActionMasked == MotionEvent.ACTION_CANCEL is false
        //resetCancelNextUpFlag returns false, we don't need to care,
        Canceled overall is false
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        //true
        final booleansplit = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) ! =0;

        TouchTarget newTouchTarget = null;

        boolean alreadyDispatchedToNewTouchTarget = false;


        // mFirstTouchTarget is not null
        if (mFirstTouchTarget == null) {
            / / a little
        } else {
         
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            / / target isn't empty
            while(target ! =null) {
                final TouchTarget next = target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    / / resetCancelNextUpFlag returns false
                    / / cancelChild intercepted decision
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    // If intercepted is true, a Cancel event is passed to the child view
                    // If intercepted is false then an up event is passed to the child view
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    // Break the loop
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue; } } predecessor = target; target = next; }}// resetTouchState is called because it is ACTION_UP
        if (canceled
                || actionMasked == MotionEvent.ACTION_UP
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            resetTouchState();
        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
          / /... slightly
        }



        returnhandled; }}Copy the code

Conclusion 10: If the ViewGroup does not intercept the Up event, all variable identifiers will be reset after the event is passed to the child view. If the ViewGroup intercepts an Up event, the child view receives the Cancel event, and the event does not pass the ViewGroup’s onTouchEvent function, after which all variable identifiers are reset.

The child view didn’t consume ACTION_DOWN

The child view doesn’t consume ACTION_DOWN events, so it has to consume ACTION_DOWN events as a ViewGroup, otherwise the event won’t get delivered here. The specific consumption event judgment needs to be explained below in view


//ViewGroup.java
public class ViewGroup {
    public boolean dispatchTouchEvent(MotionEvent ev) {


        boolean handled = false;

        // Currently returns ACTION_UP
        final int action = ev.getAction();

        final int actionMasked = action & MotionEvent.ACTION_MASK;




        final boolean intercepted;
        //mFirstTouchTarget ! = null to true
        if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
            // Flag bit received affects whether to intercept
            final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
            if(! disallowIntercept) {// An empty implementation that returns false by default
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action);
            } else {
                intercepted = false; }}else {
            intercepted = true;
        }


        Canceled overall is false
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;


        //true
        final booleansplit = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) ! =0;

        TouchTarget newTouchTarget = null;

        boolean alreadyDispatchedToNewTouchTarget = false;

        // Because we assume the consumption DWON event here
        if (mFirstTouchTarget == null) {
            // The view dispatchTouchEvent function of its parent class will be called due to the third null, and the view. DispatchTouchEvent will call onTouchEvent
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
          / / a little
        }

        // Reset some state if it is an Up event
        if (canceled
                || actionMasked == MotionEvent.ACTION_UP
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            resetTouchState();
        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
          / / a little
        }


        returnhandled; }}Copy the code

Here we analyze all the distribution paths and points for the three events in the ViewGroup. Keep in mind that ACTION_DOWN must call the onInterceptHoverEvent of the ViewGroup (regardless of the flag bit).

The view of ACTION_DOWN

We analyzed that the above View of dispatchTouchEvent ViewGroup will pass dispatchTransformedTouchEvent function call.

//view.java
class View{

	    public boolean dispatchTouchEvent(MotionEvent event) {
        boolean result = false;

      

        // Currently ACTION_DOWN
        final int actionMasked = event.getActionMasked();
        // If the event is DOWN, stop scrolling and notify the parent view's onStopNestedScroll function
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            stopNestedScroll();
        }

        / / onFilterTouchEventForSecurity as true
        if (onFilterTouchEventForSecurity(event)) {
            //ListenerInfo contains many listener callbacks
            ListenerInfo li = mListenerInfo;

            if(li ! =null  //-------- is not empty ------&& li.mOnTouchListener ! =null // Whether the user sets the onTouch function callback --
              && (mViewFlags & ENABLED_MASK) == ENABLED ----------- If the current view is enable
              && li.mOnTouchListener.onTouch(this, event) //-------- calls the user-set onTouch function, and the return value of the function determines result
                    ) {
                result = true;
            }
              // The view. OnTouchEvent is called only if the user's custom onTouch returns false
            if(! result && onTouchEvent(event)) { result =true; }} If the event is UP, stop scrolling and notify the parent view's onStopNestedScroll functionif(actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && ! result)) { stopNestedScroll(); }return result;
    }

	 //
	 private ViewParent mNestedScrollingParent;
	 public void stopNestedScroll(a) {
        if(mNestedScrollingParent ! =null) {
            mNestedScrollingParent.onStopNestedScroll(this);
            mNestedScrollingParent = null; }}}Copy the code

Conclusion 11: If the upper developer calls the view.setOnTouchListener function and returns true, view.onTouchEvent will not be called. Click events are handled in view.onTouchEvent, so click events cannot be called back.

We can see that the view. dispatchTouchEvent function is relatively simple, we only need to care about the view. onTouchEvent function, which returns the value to determine whether the event is consumed.


//View.java
public class View {
    public boolean onTouchEvent(MotionEvent event) {

        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            // As long as CLICKABLE or LONG_CLICKABLE is true, it will consume, even if view is DISABLED
            // If CLICKABLE or LONG_CLICKABLE is true, it will be consumed.
            // So it is easy to understand that CLICKABLE or LONG_CLICKABLE is true and will consume events regardless of whether the view is available
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }


        // Call the proxy class. If the proxy returns true, end the event
        // Don't worry too much here
        if(mTouchDelegate ! =null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true; }}// Take a close look at the last line of the if line return true.
        // If CLICKABLE or LONG_CLICKABLE is true, it will be consumed
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {

            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                   / / a little
                case MotionEvent.ACTION_DOWN:
                    / /.. slightly
                    break;

                case MotionEvent.ACTION_CANCEL:
                    / /.. slightly
                    break;

                case MotionEvent.ACTION_MOVE:
                    / /.. slightly
                    break;
            }
            return true;
        }
        return false; }}Copy the code

Conclusion 12: The view will be consumed if its CLICKABLE or LONG_CLICKABLE is true.

The view of ACTION_MOVE

Seems to be relatively simple, there is not much explanation

The view of ACTION_UP

//View.java
public class View {
    public boolean onTouchEvent(MotionEvent event) {


        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {

            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:

                   
                    if (mPerformClick == null) {
                        mPerformClick = new PerformClick();
                    }
                    // There is a post operation that needs special attention. The PerformClick class has an interface function that also calls the view's PerformClick directly
                    // Delay the release of the click callback to give the view time to update its state before the click event is triggered
                    if(! post(mPerformClick)) { performClick(); }break;


            }

            return true;
        }

        return false; }}Copy the code

Let’s look at PerformClick and the PerformClick function

//View.java
  private final class PerformClick implements Runnable {
        @Override
        public void run(a) { performClick(); }}// Here is a trick subview override performClick can be treated as a click to receive the click event
   public boolean performClick(a) {
      final boolean result;
      final ListenerInfo li = mListenerInfo;
      if(li ! =null&& li.mOnClickListener ! =null) {
          // Callback click function
          li.mOnClickListener.onClick(this);
          result = true;
      } else {
          result = false;
      }

    
      return result;
  }
  
Copy the code

conclusion

Conclusion 1: events must be from the Activity. The dispatchTouchEvent began handing out. (here is not out of this conclusion, but for the sake of completeness.)

Conclusion 2: ACTION_DOWN event must call Activity. OnUserInteraction.

Conclusion 3: PhoneWindow will call back to activity. onTouchEvent(action_down. ACTION_MOVE,ACTION_UP) without consuming touch events.

Conclusion 4: The onInterceptTouchEvent is called when ACTION_DOWN is not affected by FLAG_DISALLOW_INTERCEPT

Conclusion 5: FLAG_DISALLOW_INTERCEPT may affect whether onInterceptTouchEvent is called if the mFirstTouchTarget is not null, whereas the mFirstTouchTarget is not null only in ACTION_MOVE/ACTION _UP.

Conclusion 6: If the child view consumes a DOWN event and the parent viewGroup intercepts the event on MOVE, it passes a Cancel to the child view. Notice that the ViewGroup intercepts the MOVE event, but the MOVE event is not passed to the ViewGroup onTouchEvent, and subsequent events are passed to the ViewGroup. OnTouchEvent.

Conclusion 7: After a ViewGroup intercepts the first move event, it does not call the interceptor again and calls its own descendant View’s dispatchTouchEvent. And view.dispatchTouchEvent is called back to ontouchEvent

Conclusion 8: If all children of a ViewGroup do not consume ACTION_DWON events, then no distribution will be performed on the judgment children and the dispatchTouchEvent of its own descendant view will be called. And view.dispatchTouchEvent is called back to ontouchEvent

Note here: Our root view is a decorView that we can’t touch, so when our child view doesn’t consume DWON, the decorView doesn’t send any events, but the decorView can receive subsequent events.

Conclusion 9: With the presence of a decorView, if the DWON event does not have any view consumption, no event is issued below. But the decorView receives subsequent events.

Conclusion 10: If the ViewGroup does not intercept the Up event, all variable identifiers will be reset after the event is passed to the child view. If the ViewGroup intercepts an Up event, the child view receives the Cancel event, and the event does not pass the ViewGroup’s onTouchEvent function, after which all variable identifiers are reset.

Conclusion 11: If the upper developer calls the view.setOnTouchListener function and returns true, view.onTouchEvent will not be called. Click events are handled in view.onTouchEvent, so click events cannot be called back.

Conclusion 12: View CLICKABLE or LONG_CLICKABLE will be consumed if it is true.

Viewgroups can only distribute events, whereas views can only choose to consume