The ViewGroup,View under Android Event Distribution Mechanism Source Code

Posted by timmy2 on Sat, 01 Jun 2019 23:00:40 +0200

In the last article, we analyzed the process of event transfer from mobile phone hardware to DecroView. Then we analyzed how ViewGroup and View transfer and handle touch events.
The importance of View's event distribution mechanism is self-evident, interviews, usually do are often contacted. Usually they write according to the code, but they don't know many principles. For example, why does onTouch execute first than OnClick? Why is OnClick no longer executed after onTouch returns true? What's the difference between onTouch and onTouchEvent? These problems can be said to have been troubling many people. Today, we write demo, look at the effects and follow the source code to analyze one problem after another. The source code of this article is from API24. At the end of the paper, the whole process of analyzing touch events from hardware to View processing will be added.

Adhering to the idea that people have a cool experience after planting trees, first of all, the essence of their summing up and the summary of the great God are given first.
In Android, there are three main methods for event distribution of View: dispatch TouchEvent (), onIntercept TouchEvent (), and onTouchEvent(). When we click on a child View control, the first call is dispatchTouchEvent(), which is the parent ViewGroup of the view. The dispatchTouchEvent() calls onInterceptTouchEvent() internally to decide whether to intercept the event. If intercepted, the onTouchEvent() of ViewGroup is called to process the event, and the event is blocked and no longer passed downwards, so it may generate you to click on a certain event. Button, button processing action did not occur, parent control processing event triggered the situation. If the parent ViewGroup does not intercept events, otherwise dispatchTouchEvent() of the child View is called, you can refer to the following figure:

Come from http://blog.csdn.net/huachao1001
——————————- A cool dividing line
After summarizing, to tell the truth, only those gods who had studied it before and looked back remembered what it was. Did the newly born people who had not known it look at it for half a day or say something? Then we will write a demo and analyze it with the source code while looking at the effect.
—————————— Another cool splitting line————————————

ViewGroup

First, let's define a layout with only one Button button and a custom Layout on the periphery. We call it Test Layout. For the time being, it inherits only LineLayout without any code.


TestLayout code:

public class TestLayout extends LinearLayout {

    public TestLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public TestLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}

Activity code:

public class Main6Activity extends AppCompatActivity {

    Button mButton;
    TestLayout mTestLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main6);
        mButton = (Button) findViewById(R.id.test_bt);
        mTestLayout = (TestLayout) findViewById(R.id.test_layout);

        //TestLayout Register onTouchListener
        mTestLayout.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.d("touchEvent", "mTestLayout Touch Event Executed!implement Action by:" + event.getAction());
                return false;
            }
        });

        //mButton registers OnClickListener        
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("touchEvent", "button The click event was executed!");
            }
        });
        //mButton Register onTouchListener        
        mButton.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.d("touchEvent", "button Touch Event Executed!implement Action by:" + event.getAction());
                return false;
            }
        });
    }
}

Click on the button to run and see the log:

First we see that the onTouch method of the button executes first (two events are pressed and raised by a finger), and then the onClick method of the button is executed. The touch event of the parent layout testLayout was not triggered.
emmm.......
Ha-ha? Didn't it mean that the good events were given to viewGroup first? Why didn't this all react? What about registering touch events for the parent layout? Don't be too busy. In this case, let's take a look at the source code of ViewGroup's dispatchTouchEvent() method.

 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
                ...//Other code

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
//Code1place---------------------------
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

We'll post the key code, and we'll start to analyze it. In code 1 above, if you press the event, you will call cancelAndClearTouchTargets(ev) method, which will empty mFirstTouchTarget. This variable is very important and will be explained later. Then look down:

            // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
//Code2place---------------------------
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }
          ...//Other code

Next, the onInterceptTouchEvent() method at code 2 is invoked to determine whether the touch event is intercepted when the event is pressed or mFirstTouchTarget is not empty. Let's follow up and see:

  public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }

Here we can see that this method normally returns false by default. So this also shows that ViewGroup does not intercept touch events by default. So in the previous demo effect, it would naturally occur that the parent layout did not do any processing and directly passed the touch event down to Button. If we override the onInterceptTouchEvent() method of the parent layout, it returns true. That means that the parent layout of ViewGroup intercepts touch events, and the events are no longer passed down to the child View. Let's continue to change demo to see the effect:
Override the onInterceptTouchEvent() method in TestLayout to return false:

public class TestLayout extends LinearLayout {

   ...//Other code

  //Rewrite the method and return true
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return true;
    }
}

Let's click the button again to see the effect:


As you can see from the picture above, when TestLayout intercepted the touch event, neither Button's touch nor click event was triggered. So we can conclude that event distribution is passed from parent layout to child control.
Back to the ViewGroup code, let's move on to the following:

Code3place---------------------------
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            if (!canceled && !intercepted) {
                        ...//Other code

                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                      ...//Other code
//Code4place---------------------------
                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        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);
                           ...//Other code
                        }

At code 3, a new TouchTarget variable is defined. It then determines whether the event is cancelled and interrupted, if not. Then execute the code in the if condition.
At code 4, a child renCount of type int is defined to indicate how many sub-views the ViewGroup has. Next, determine whether the number of child renCount is 0 (that is, whether the ViewGroup has child Views). If so, then execute the code to find which collective child View the event should be passed to.

Continue to look at the source code:

Code5place---------------------------
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
//Code6place---------------------------
                                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;
                    }
                }
            }

We can see that the dispatchTransformedTouchEvent() method is called at code 5. What method is this? Let's follow up:

   private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
...//Other code
        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;
        }
....//Other code

As we can see here, when the child is empty, it means that no touching event has been found within the scope of a specific sub-view. At this point, the super.dispatchTouchEvent() method is called, and the parent class of ViewGroup is the View object. Indicates that the event has been handled by ViewGroup itself at this time. If the child is not empty, the dispatchTouchEvent() method of the child View is called to handle it. When the touch event is processed, a Boolean value handled is returned, which indicates whether the child View consumes the event. How do you determine whether a subview consumes events? If the onTouchEvent() of the child View returns true, it indicates that the event was consumed.

Next, at code 6, the addTouchTarget() method is called to assign the new TouchTarget variable. Let's follow up and see:

  private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

We can see that first we find the TouchTarget object of the specific child, then mFirstTouchTarget points to the child, and then returns the target.

Go back to the dispatchTouchEvent() method of ViewGroup and look down:

Code7place---------------------------
            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
//Code8place---------------------------
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                    }
                             ...//Other code
                }
            }

          ...//Other code
        return handled;
    }

The mFirstTouchTarget is judged to be empty at code 7. Obviously, in the previous analysis, if our touch event is not intercepted, we assign mFirstTouchTarget in the addTouchTarget() method, so this code will not be executed. Only where the ViewGroup type does not intercept or click on the empty space of the ViewGroup (no child controls are clicked, and the event does not occur within the scope of any child View). In both cases, mFirstTouchTarget will not be changed to null before executing the code in the if condition. In other words, if the mFirstTouchTarget == null condition holds, the code that executes handles both cases where ViewGroup intercepts events or all sub-Views do not consume events. The dispatchTransformedTouchEvent() method is called here and handled by ViewGroup. If mFirstTouchTarget is not empty, it indicates that a child has handled the ACTION_DOWN touch event, then execute the code of the else block.

At code 8, you can see from the above analysis that if the child View consumes ACTION_DOWN touch events, alreadyDispatchedToNewTouchTarget will be modified to true and target == newTouchTarget is also valid. So it means that this is an ACTION_DOWN event. If there is an event that is not valid and indicates that it is other than ACTION_DOWN, then continue to distribute other events to the sub-View process here.
So far, we have analyzed the dispatchTouchEvent() method of ViewGroup. To sum up:
* When an event is passed to our ViewGroup, the dispatchTouchEvent() method is called, in which the onInterceptTouchEvent() method (default is false) is first called to determine whether the ViewGroup intercepts the event. There are two results: 1. If the method returns true, that is to intercept, then the event is passed to this end, and no longer to the child View. 2. If the event is not intercepted by placing back false, then the event is passed to the sub-View, which is processed by the sub-View.

View

After analyzing the dispatchTouchEvent() source code of ViewGroup, let's analyze the dispatchTouchEvent() method of ViewGroup.

Let's change the demo code, first remove the previous Button and rewrite a custom View inherited from Button to rewrite the onTouchEvent() method (remember to change our previous TestLayout interception event method to return false, otherwise the event will be intercepted):

public class TestButton extends Button {

    public TestButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public void init() {
        setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("touchEvent", "button The click event was executed!");
            }
        });

        setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.d("touchEvent", "button A onTouch Method executed!implement Action by:" + event.getAction());
                return false;
            }
        });
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("touchEvent", "button A onTouchEvent Method executed!implement Action by:" + event.getAction());
        return super.onTouchEvent(event);
    }
}

The layout is simple as follows:

<com.demo.dltlayoutlayoutparams.TestLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/test_layout"
    tools:context="com.demo.dltlayoutlayoutparams.Main6Activity">

    <com.demo.dltlayoutlayoutparams.TestButton
        android:id="@+id/test_bt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</com.demo.dltlayoutlayoutparams.TestLayout>

The code for Activity is as follows:

public class Main6Activity extends AppCompatActivity {

    TestLayout mTestLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main6);
        mTestLayout = (TestLayout) findViewById(R.id.test_layout);

        mTestLayout.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.d("touchEvent", "mTestLayout Touch Event Executed!implement Action by:" + event.getAction());
                return false;
            }
        });
    }
}

Let's run demo and click on the button to see the log results:

As shown above, it is obvious that the onTouch method is executed first, then OntouchEvent() is executed, and then click() is executed because our fingers are pressed and then raised. So there are two events that have been dealt with one after another.

So why does the onTouch() method execute first than the onTouchEvent() method? The onTouch() method returns a Boolean value. What's the use? (Can't you execute onTouch first because it has fewer letters than onTouchEvent()? ) With these doubts, let's analyze the dispatchTouchEvent() method of View.

The source code is posted here first.

   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;
            }
            //noinspection SimplifiableIfStatement
            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;
            }
        }

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

emmm... First of all, you can see that the source code actually contains a lot of annotations and a lot of other code to do some judgment and processing work. Here I extract the core process code to see:

  //Listener Info is a wrapper class that encapsulates various listeners for View.
  //We also set up onTouchListener to ListenerInfo.
   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;
            }

As you can see, first of all, if the view's OnTouchListener is not empty and our view is editable (in the Enable state) and the overridden onTouch() method returns true, then the result is set to true. Then the onTouchEvent() method is no longer executed. So the reason why onTouch() method takes precedence over onTouchEvent() method is here. Let's set the onTouch() method to return true to see the effect:

setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.d("touchEvent", "button A onTouch Method executed!implement Action by:" + event.getAction());
                //Before modification
                return false;
                //After modification
                return true;
            }
        });
    }

Run and click the button:


You can see that our onTouch method returns true, and then the onTouchEvent() method is no longer executed.

Eh? Suddenly, how can the onClick() method disappear? Didn't I change the click event? So why didn't the onClick() method execute when our onTouch() method returned true? We followed up on the onTouchEvent() method.

First put the source code of onTouchEvent() method:

 public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
        }
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true, x, y);
                       }

                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0, x, y);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_MOVE:
                    drawableHotspotChanged(x, y);

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            setPressed(false);
                        }
                    }
                    break;
            }

            return true;
        }

        return false;

    }

emmm... ... seems a little long?? It's a big sight to see. That long code. Probably we can not analyze them one by one, then we will filter out some irrelevant codes to extract the essence.

 public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

        .......//Unimportant code

        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                                .......//Unimportant code

                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }
        .......//Unimportant code
        return false;

    }

After such extraction, key code appears. We can see that in the case MotionEvent.ACTION_UP case above, we get the execution of the performClick() method. We continue to follow up to see the code:

 public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

So far, it's clear that the onClick() method of onClickListener that we set up in our code is executed here. Therefore, if our onTouch() method returns true, the onTouchEvent() method will not be executed, and the onClick() method in the onTouchEvent() method will not be executed!

Then the problem comes again. The return value of. onTouch() affects whether onTouchEvent() is executed, so what is the return value of onTouchEvent()? It seems that this return value does not affect the execution of click events? Let's try false and true instead.

@Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("touchEvent", "button A onTouchEvent Method executed!implement Action by:" + event.getAction());
        //Before modification
        return super.onTouchEvent(event);
        //After the first revision
        return false;
        //After the second amendment
        return true;
    }

Let's take a look at the effect:


We can see that the click events disappear after the first change to true or false, which is very understandable. Because the onClick() method was previously analyzed and called in the onTouchEvent() method of View. Now we rewrite the onTouchEvent() method without calling the super.onTouchEvent() method, so the click event will certainly not be called.

Secondly, we found that onTouch and onTouchEvent() only handled the pressing action, but did not continue to handle the subsequent lifting action when we returned to false. And the onTouch() lifting action of TestLayout is executed, but the lifting action is not handled.

In fact, this is because onTouchEvent() when we return false, indicating that our child View does not handle events, it will return all events to the parent layout. When the down event is not consumed, the onTouch() method is no longer executed for subsequent events such as move,up, and so on. So here we hand events directly to the parent layout, and here our parent layout TestLayout's onTouch() method set in Activity also returns false. So we will continue to upload events, and interested comrades here can return the onTouch() method of TestLayout to true to see the effect. (The article is too long.) )
When we return true, it means that we want to handle the event, so the event will not be passed back to the parent layout.

—————————— A splitting line pretending to be gorgeous——————————
Additional analysis: The specific reason is that the onTouch() method is called at code 5 of the original ViewGroup source code, which is contained in the if condition above code 4, provided that the event is a finger press event or other event to execute. Then the onTouchEvent() method invoked in the onTouch method returns false if onTouchEvent() returns false, and the onTouch() method also returns false (see the dispatchTouchEvent() source code of View above). If the condition judgment at code 5 is not valid, then the mFirstTouchTarget variable will not be modified to null, so it is handed over to the parent layout at ViewGroup source code 7. Others such as finger movement, finger lifting events will not go to code 4, 5. So jump directly to code 7, and mFirstTouchTarget is still null. So when the child View does not handle down events, all events are no longer handled by the child View. In other words, the mFirstTouchTarget assignment is not null unless the first down event processing in onTouchEvent() returns true. Then the subsequent events are handed over to the child View at code 7.
—————————— A pretending gorgeous ending line——————————

Hu... So far, we have finished the process of transmitting and returning the event distribution of ViewGroup and View, which is also the end of scattering flowers.
Finally, summarize View:
* View is executed in the order of onTouch() - > onTouchEvent() - > onClick (). If onTouch() returns true to indicate that the event was consumed, it is no longer passed to onTouchEvent() for execution. It's like Xiao Ming and Xiao Gang going to buy ice cream. Xiao Ming takes a bite first. He can choose whether to give it to Xiao Gang or leave it behind.
* If View consumes only down events, not other events, then other events will not be passed back to ViewGroup, but will disappear silently. We know that once downs are consumed, all subsequent events in the series will be handed over to the View, so if events other than downs are not handled, they will be "abandoned".

  • A View, in onTouchEvent, if it returns false for the first down event, the next event series will not be handed over to the View.

  • View has no onInterceptTouchEvent method. Because it is not a parent control, there is no need to decide whether to intercept it or not.

ps: This article also refers to the articles of Guo Lin and other gods.
The Android Event Distribution Mechanism is fully parsed to give you a thorough understanding of the source code perspective.
Understand the View Event System thoroughly!

Topics: Android Mobile React REST