Android event delivery mechanism: dispatchTouchEvent source code analysis with specific examples

**Note: Because it is parsing the source code, this article requires a certain understanding of Android's event delivery mechanism. **Of course, I will also briefly summarize the knowledge of event delivery:

1. Simple summary

  1. The same sequence of events : refers to a series of events generated in the process from the moment the finger touches the screen to the moment the finger leaves the screen. This event starts with the down event and contains an indefinite number of moves in the middle. event, and finally ends with the up event.
  2. The event distribution of click events is the distribution of the same event sequence. This process is completed by three very important methods: dispatchTouchEvent, onInterceptTouchEvent, onTouchEvent.
  3. When a click event is generated, the delivery process follows the following sequence: Activity-ViewGroup-View. After the top-level view receives the event, it will be distributed according to the event distribution mechanism.
  4. dispatchTouchEvent(): Indicates whether this layer or the lower layer has the ability to handle events. Both view and ViewGroup have this method. , note that this method indicates whether it is capable . The distribution of events is affected by the onInterceptTouchEvent method of this layer, the onTouchEvent return value and the dispatchTouchEvent result of the subordinate view.
    This method returns true, indicating that the event may have been processed by this layer or a lower layer, and the event is no longer distributed.
    There are the following situations:
    1.) onInterceptTouchEvent intercepted the event (returns true), and onTouchEvent of this layer processed the event (returned true) );
    2.) The return value of dispatchTouchEvent of the lower element is true, and the event is handled by the lower layer.
    This method returns false, indicating that the event is not handled by this layer or the lower layer. The event is thrown to the parent layer
    in the following situations:
    1.) onInterceptTouchEvent intercepts the event, but the onTouchEvent of this layer does not process the event (returns false), which means that this layer does not have the ability to handle the event, and the event is thrown to the parent container deal with.
    2.) onInterceptTouchEvent did not intercept the event (returned false), but the lower layer's dispatchTouchEvent returned false because of the layer's onTouchEvent, indicating that the lower layer was not capable of handling the event.

Note: If the dispatchTouchEvent() method of a component returns true, then subsequent events in the event sequence will be processed by the component, unless we override dispatchTouchEvent() to return false or intercept the event through onInterceptTouchEvent().

  1. onInterceptTouchEvent(): Whether the ViewGroup intercepts the event (the default is false, which means no interception is performed and is passed directly to the dispatchTouchEvent of the subview). Only ViewGroup has this method.
    Return true, intercept the event, and hand over the intercepted event to the onTouchEvent of this layer control for processing;
    return false, do not intercept the event, the event is distributed to the sub-View and distributed by the dispatchTouchEvent of the sub-View.
    By default, super.onInterceptTouchEvent(ev) is returned. The event will not be intercepted by default and will be handled by dispatchTouchEvent of the child View.

If the current view intercepts the event, subsequent events in the same event sequence will be directly handed over to the view for processing, and the onInterceptTouchEvent method will no longer be called.

2. Source code analysis of dispatchTouchEvent method

There is a very important variable mFirstTouchTarget that requires additional attention during the parsing process.
mFirstTouchTarget represents a subcomponent in an event series that can handle touch events. If mFirstTouchTarget is not null, it means that we have found the subcomponent.

The source code of the dispatchTouchEvent method is as follows: The source code is very long. I divided it into four steps and added some comments to help understand. If you don’t want to read the source code, you can skip the source code and look directly at the following four steps:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    
    
    if (mInputEventConsistencyVerifier != null) {
    
    
        mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
    }


    if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
    
    
        ev.setTargetAccessibilityFocus(false);
    }

    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
    
    
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;
//第一步:
//ACTION_DOWN是事件序列的开端,所以触摸事件ACTION_DOWN时进行初始化操作。
//cancelAndClearTouchTargets和resetTouchState方法都用于清空上一次触摸事件的缓存数据。会把 mFirstTouchTarget 清空为null
        if (actionMasked == MotionEvent.ACTION_DOWN) {
    
    

            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

//第二步:检查触摸事件是否需要拦截
        final boolean intercepted; //定义一个变量intercepted来标志是否需要拦截触摸事件。
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
    
    
//事件为ACTION_DOWN或者mFirstTouchTarget不为null,
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//判断disallowIntercept禁止拦截   
//因为在其他地方可能调用了requestDisallowInterceptTouchEvent(boolean disallowIntercept)
//从而禁止执行是否需要拦截的判断。字面意思,禁止调用owInterceptTouchEvent()方法进行触摸事件的拦截。
            if (!disallowIntercept) {
    
    
//如果可以调用owInterceptTouchEvent(),则调用onInterceptTouchEvent()方法判断是否拦截这个触摸事件
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); 
            } else {
    
    
//不允许调用owInterceptTouchEvent()方法,不拦截事件
                intercepted = false;
            }
        } else {
    
    
//当不是ACTION_DOWN事件并且mFirstTouchTarget为null(即没有可以处理触摸事件的子组件)时    
//设置 intercepted = true表示ViewGroup拦截触摸事件。
            intercepted = true;
        }

        if (intercepted || mFirstTouchTarget != null) {
    
    
            ev.setTargetAccessibilityFocus(false);
        }

        // 判断是不是ACTION_CANCEL事件
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

// 第三步:进行事件分发:
        final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
                && !isMouseEvent;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        if (!canceled && !intercepted) {
    
    
  //不是ACTION_CANCEL事件 并且ViewGroup不拦截,那我们可以去找一个子组件处理触摸事件。
            View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                    ? findChildWithAccessibilityFocus() : null;

            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
    
    
//如果是ACTION_DOWN事件
//下面代码概述:
// 开启了一个循环,循环所有的子View判断哪个子View可以处理触摸事件。
子view是否可以处理触摸事件肯定也是调用子view 的dispatchTouchEvent()方法判断,该循环中的dispatchTransformedTouchEvent()里面就是调用的子view 的dispatchTouchEvent()方法,如果返回true,我们就找到了可以触摸事件的子view ,并将该子view 赋值给mFirstTouchTarget。
                final int actionIndex = ev.getActionIndex(); 
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                        : TouchTarget.ALL_POINTER_IDS;

           
                removePointersFromTouchTargets(idBitsToAssign);

                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
    
    
                    final float x =
                            isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
                    final float y =
                            isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
        
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    for (int i = childrenCount - 1; i >= 0; i—) {
    
    
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);


                        if (childWithAccessibilityFocus != null) {
    
    
                            if (childWithAccessibilityFocus != child) {
    
    
                                continue;
                            }
                            childWithAccessibilityFocus = null;
                            i = childrenCount - 1;
                        }

                        if (!child.canReceivePointerEvents()
                                || !isTransformedTouchPointInView(x, y, child, null)) {
    
    
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }

                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
    
    
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }
                        resetCancelNextUpFlag(child);
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
    
    

                            mLastTouchDownTime = ev.getDownTime();
                            if (preorderedList != null) {
    
    
   
                                for (int j = 0; j < childrenCount; j++) {
    
    
                                    if (children[childIndex] == mChildren[j]) {
    
    
                                        mLastTouchDownIndex = j;
                                        break;
                                    }
                                }
                            } else {
    
    
                                mLastTouchDownIndex = childIndex;
                            }
                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }


                        ev.setTargetAccessibilityFocus(false);
                    }
                    if (preorderedList != null) preorderedList.clear();
                }

                if (newTouchTarget == null && mFirstTouchTarget != null) {
    
    

                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
    
    
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }

//第四步,后续事件处理,我们通过第三步可能找到一个可以处理子事件的子view
        if (mFirstTouchTarget == null) {
    
    
如果没有找到,则调用dispatchTransformedTouchEvent()方法,不过这里传的参数是null,该方法内部如果是null,就会判断ViewGroup自身是否可以处理触摸事件。
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
    
    
// 如果找到了,则调用 dispatchTransformedTouchEvent()方法,
//并传值是mFirstTouchTarget,dispatchTransformedTouchEvent内部会调用子View的dispatchTouchEvent()方法,完成事件的分发,
//因为我们在第三步为了找到一个可以出发触摸事件的子View,可能已经调用过dispatchTransformedTouchEvent()方法分发过一次了,所以在第三步和第四步都加了一个boolean值 alreadyDispatchedToNewTouchTarget,避免进行重复分发。
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {
    
    
                final TouchTarget next = target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
    
    
                    handled = true;
                } else {
    
    
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
    
    
                        handled = true;
                    }
                    if (cancelChild) {
    
    
                        if (predecessor == null) {
    
    
                            mFirstTouchTarget = next;
                        } else {
    
    
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }

        // Update list of touch targets for pointer up or cancel, if needed.
        if (canceled
                || actionMasked == MotionEvent.ACTION_UP
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
    
    
            resetTouchState();
        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
    
    
            final int actionIndex = ev.getActionIndex();
            final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
            removePointersFromTouchTargets(idBitsToRemove);
        }
    }

    if (!handled && mInputEventConsistencyVerifier != null) {
    
    
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    }
    return handled;
}




//另一个重要的方法:
//该方法很简单,如果传入的child不是null,调用child的dispatchTouchEvent()方法,如果传入的child是null,则调用ViewGroup基类也就是View类的dispatchTouchEvent()方法,判断当前ViewGroup自身可以不可以处理触摸事件。
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 (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;
}

Let’s sort out the above code process

dispatchTouchEvent source code steps:

The first step: The ACTION_DOWN event comes, indicating that a new touch series has begun. ViewGroup clears the cache of the last touch event series, mainly the View that handled the touch event in the last touch event: mFirstTouchTarget.

Step 2: If it is an ACTION_DOWN event or mFirstTouchTarget is not empty (that is, the View that handles the touch event has been found through step 3 below), you need to call the onInterceptTouchEvent() method to determine whether to intercept the touch event.

Step 3: If it is not intercepted and it is an ACTION_DOWN event (the start of a touch event), we need to find a sub-View that can handle the event. The method must be to loop through all sub-Views and call the dispatchTouchEvent() method on them. At this time, the touch event has been distributed to the subview.
This step will only be called once during the ACTION_DOWN event, that is, a touch event series will only find a sub-View that can be processed.
This is why the dispatchTouchEvent() method returns true, then subsequent events in the event sequence will be handed over to a component for processing, because the dispatchTouchEvent() method will only look for a sub-View that can handle touch events during the ACTION_DOWN event, and other events will not Will search for it.

Step 4: This is an if judgment to determine whether mFirstTouchTarget (that is, after the third step, we found a sub-View that can handle touch events) is null, then subsequent touch events ACTION_MOVE and ACTION_UP will call this sub-View again. dispatchTouchEvent() method, which completes the distribution of subsequent touch events to subviews.
If we go through the third step and do not find a sub-View that can handle touch events, we must determine whether the ViewGroup itself can handle the touch event. Because the ViewGroup inherits from View, the dispatchTouchEvent() method of the View base class is called to determine whether the ViewGroup itself can handle the touch event. Can handle touch events.

The core method of the third and fourth steps is: dispatchTransformedTouchEvent() method.
The core code of this method is:

if (child == null) {
    
    
    handled = super.dispatchTouchEvent(event);
    //mFirstTouchTarget是null,我们没有找到可以处理触摸事件的子View,交给ViewGroup处理,判断ViewGroup可以不可以处理触摸事件
} else {
    
    
    handled = child.dispatchTouchEvent(event);
    //mFirstTouchTarget不是null,我们找到了一个可以处理触摸事件的子View,那就分发事件给他
}

What we talked about above are all situations where there is no interception. What happens if there is interception? Interception is divided into two situations:

  1. ) The first is to intercept directly from the ACTION_DOWN event. Through the third step, we know that when ACTION_DOWN comes, only if (!canceled && !intercepted) is satisfied will we find a sub-ViewmFirstTouchTarget that can handle the touch event. If we start from ACTION_DOWN is intercepted, then this child view will not be found, then mFirstTouchTarget is null,
    so the fourth step of if judgment will be executed
    if (mFirstTouchTarget == null) { handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS ); } Through the core code of dispatchTransformedTouchEvent, we know that when a null is passed in, dispatchTouchEvent of the base class View class of the current ViewGroup will be called to handle its own touch events.



  2. ) The second type is not to intercept the ACTION_DOWN event, but to intercept it in ACTION_MOVE. At this time, mFirstTouchTarget may not be null, so the else branch of the first case is entered:

TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
    
    
    final TouchTarget next = target.next;
    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
    
    
        handled = true;
    } else {
    
    
        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                || intercepted;
        if (dispatchTransformedTouchEvent(ev, cancelChild,
                target.child, target.pointerIdBits)) {
    
    
            handled = true;
        }
        if (cancelChild) {
    
    
            if (predecessor == null) {
    
    
                mFirstTouchTarget = next;
            } else {
    
    
                predecessor.next = next;
            }
            target.recycle();
            target = next;
            continue;
        }
    }
    predecessor = target;
    target = next;
}

The core code of the above code is as follows:

        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                || intercepted;
        if (dispatchTransformedTouchEvent(ev, cancelChild,
                target.child, target.pointerIdBits)) {
    
    
            handled = true;
        }

You can see that the event is still distributed through the dispatchTransformedTouchEvent() method.
The code behind the above code is that if we intercept the touch event, mFirstTouchTarget will be set to null. It is the following code: (This is why if the current view intercepts the event, subsequent events in the same event sequence will be directly handed over to the view for processing, because mFirstTouchTarget has been reproduced as null)

if (cancelChild) {
    
    
    if (predecessor == null) {
    
    
        mFirstTouchTarget = next;
    } else {
    
    
        predecessor.next = next;
    }
    target.recycle();
    target = next;
    continue;
}

Yes, if you want to intercept touch events, you will pass in a false to dispatchTransformedTouchEvent. In the dispatchTransformedTouchEvent method, if you pass in false, an ACTION_CANCEL event will be distributed instead of the original event.
The processing code of dispatchTransformedTouchEvent() method is as follows:

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;
}

Example demonstration:

class MyLinearLayout(
    context : Context , @Nullable attrs : AttributeSet? ,
    defStyleAttr : Int ,
) : LinearLayout(context , attrs , defStyleAttr) {
    
    
    constructor(context : Context) : this(context , null , -1)
    constructor(context : Context , @Nullable attrs : AttributeSet?) : this(context , attrs , -1)

    init {
    
    
        orientation = VERTICAL
        setBackgroundColor(Color.parseColor("#00FF00"))
    }

    override fun dispatchTouchEvent(ev : MotionEvent?) : Boolean {
    
    

        if (ev!!.action == MotionEvent.ACTION_DOWN) {
    
    
            Log.e("父组件分发了DOWN事件" , "--------")
//            return true
        } else if (ev.action == MotionEvent.ACTION_MOVE) {
    
    
            Log.e("父组件分发了MOVE事件" , "--------")
//           return false
        } else if (ev.action == MotionEvent.ACTION_UP) {
    
    
            Log.e("父组件分发了UP事件" , "--------")
        }
        return super.dispatchTouchEvent(ev)
    }

    override fun onInterceptTouchEvent(ev : MotionEvent?) : Boolean {
    
    
        if (ev!!.action == MotionEvent.ACTION_DOWN) {
    
    
            Log.e("父组件拦截了DOWN事件" , "--------")
            // return true
        } else if (ev.action == MotionEvent.ACTION_MOVE) {
    
    
            Log.e("父组件拦截了MOVE事件" , "--------")
//            return true
        } else if (ev.action == MotionEvent.ACTION_UP) {
    
    
            Log.e("父组件拦截了UP事件" , "--------")
//            return true
        }
        return super.onInterceptTouchEvent(ev)
    }

}
class MyView(
    context : Context , @Nullable attrs : AttributeSet? ,
    defStyleAttr : Int ,
) : View(context , attrs , defStyleAttr) {
    
    
    constructor(context : Context) : this(context , null , -1)
    constructor(context : Context , @Nullable attrs : AttributeSet?) : this(context , attrs , -1)

    init {
    
    
        setBackgroundColor(Color.parseColor("#FF0000"))
    }

    override fun onMeasure(widthMeasureSpec : Int , heightMeasureSpec : Int) {
    
    
        setMeasuredDimension(500 , 500)
    }

    override fun dispatchTouchEvent(event : MotionEvent?) : Boolean {
    
    
        if (event!!.action == MotionEvent.ACTION_DOWN) {
    
    
            Log.e("子组件分发了DOWN事件" , "--------")
        } else if (event.action == MotionEvent.ACTION_MOVE) {
    
    
            Log.e("子组件分发了MOVE事件" , "--------")
        } else if (event.action == MotionEvent.ACTION_UP) {
    
    
            Log.e("子组件分发了UP事件" , "--------")
        }else if (event.action == MotionEvent.ACTION_CANCEL) {
    
    
            Log.e("子组件分发了Cancel事件" , "--------")
        }
        return super.dispatchTouchEvent(event)
    }
    
}
<LinearLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.cxkj.myapplication.MyLinearLayout
        android:layout_width="match_parent"

        android:layout_height="wrap_content">

        <com.cxkj.myapplication.MyView
            android:id="@+id/myView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">

        </com.cxkj.myapplication.MyView>

    </com.cxkj.myapplication.MyLinearLayout>

</LinearLayout>
val myView = findViewById<MyView>(R.id.myView)
myView.setOnClickListener {
    
    
    Log.e("子view被点击了" , "--------")
}

Above code:
We defined a green LinaearLayout, rewritten the dispatchTouchEvent and onInterceptTouchEvent events, and added printing and observation.
A red View was placed, rewriting dispatchTouchEvent, and printing and observation were added.
1.) We intercept Action_Move in the parent component, we release the // return true annotation in the code below of MyLinearLayout, and then we touch MyView. What will happen?

else if (ev.action == MotionEvent.ACTION_MOVE) {
    
    
    Log.e("父组件拦截了MOVE事件" , "--------")
    return true
}

Print result:
The parent component distributed the DOWN event: --------
The parent component intercepted the DOWN event: --------
The child component distributed the DOWN event: --------
The parent component The MOVE event is distributed: --------
The parent component intercepts the MOVE event: --------
The child component distributes the Cancel event: --------
The parent component distributes the MOVE event: --------
The parent component distributed the MOVE event: --------
The parent component distributed the UP event: --------

It can be seen that the DOWN event has completed normal distribution, and the first MOVE event was only distributed by the ViewGroup, and then an additional Cancel event was distributed to the sub-View, and then the subsequent MOVE events no longer reached the sub-View. This The above source code logic is verified (after interception, a Cancel event will be distributed and mFirstTouchTarget will be reproduced as null).

So what if we just click lightly? What will happen to printing without any MOVE operation? The print result is as follows:
The parent component distributed the DOWN event: --------
The parent component intercepted the DOWN event: --------
The child component distributed the DOWN event: --------
Parent The component distributed the UP event: --------
The parent component intercepted the UP event: --------
The child component distributed the UP event: --------
The child view was clicked: --------
Yes, because there is no MOVE event and it has not been intercepted, mFirstTouchTarget has not been reset to null, which allows the UP event to be distributed normally to the subcomponent.

2.) What if we intercept directly from the DOWN event? We release the // return true annotation in the code below of MyLinearLayout, and then we touch MyView. What will happen?

   if (ev!!.action == MotionEvent.ACTION_DOWN) {
    
    
            Log.e("父组件分发了DOWN事件" , "--------")
//            return true
        }

Print result:
The parent component distributed the DOWN event: --------
The parent component intercepted the DOWN event: --------

It's amazing that MyLinearLayout can only distribute DOWN events. Why is this? Because we intercepted the event from the beginning, so mFirstTouchTarget =null, all events will be processed by MyLinearLayout itself, but we did not add a click event to MyLinearLayout, that is, MyLinearLayout's onTouchEvent returns false, indicating that it cannot handle touch events. Remember that MyLinearLayout also has a parent layout. For the parent layout of MyLinearLayout, it will also perform the above process. When the parent layout of MyLinearLayout knows that MyLinearLayout cannot handle touch events, it will no longer distribute subsequent events to MyLinearLayout, so The above situation occurred.

Guess you like

Origin blog.csdn.net/weixin_43864176/article/details/123771939