Detailed explanation of View distribution mechanism in Android

Another day of dozing off

Waving your fingertips, composing the art of fingertips

Due to my limited ability and knowledge, I will record what I understand and know;

After reading this chapter, you will gain:

  1. When is the Click event in the View triggered and called
  2. Why are the View processes executed by Button and TextView different?
  3. How to solve the sliding conflict perfectly
  4. What is the transfer process when view transfers events

Into the title

1. Diagram

insert image description here
This is a rough flowchart of event distribution, but it needs to be subdivided.

2. Outline the event distribution mechanism

1. Toch events are encapsulated into MotionEvent objects, including Tonch's position, time, history, and multi-touch;
2. Event types are divided into: ACTION_DOWN (press), ACTION_UP (lift), ACTION_MOVE (move), etc. , these three are more commonly used;
3.dispatchTouchEvent(), which means to distribute and deliver the event;
4.InterceptTouchEvent(), means to intercept an event and not pass it down;
5.onTouchEvent(), means to consume the event;

First, the event is transmitted from the current Activity.dispatchTouchEvent(). As long as the current event is not intercepted, it will be transmitted along the top ViewGroup and handed over to the ViewGroup for processing. The ViewGroup does not intercept the event by default, and it will be transmitted downward (child view). Event, the ViewGroup of the parent class can intercept the event by returning true through InterceptTouchEvent(), then it cannot continue to pass the event down and consume the current event. If it is not intercepted, it will continue to pass down. () returns true to process the consumption event, if the current view's Touch monitor is not null, the current view is available, and the current view's onTouch method returns true, then the default consumption event is not called onTonchEvent method. If the event is not intercepted from top to bottom, and the child view does not consume the event, the event will be passed upwards, and the parent container ViewGroup can consume and process this time. If the ViewGroup has not processed the event, it will continue to be passed to the Activty. onTonchEvent() for processing. Among the MOVE, UP, and DOWN in onTonchEvent(), if one returns false, it will not continue to execute. For example, if false is returned in down, then up and move will not be executed.

3. Take Button as an example for actual analysis

btn.setOnTouchListener(new View.OnTouchListener() {
    
    
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
    
    
                return true;
            }
        });

It can be seen that the return value here is true, the current btn is available, and the listening event is also set, and onTouch also returns true, then it will be passed upwards and the ViewGroup will decide whether to intercept it. If it is not intercepted, it will be passed down and passed to the current When viewing, the opportunity consumes the event by itself and no longer passes it down.
Let's take a look at part of the source code#dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent event) {
    
    
    // ...
            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;
            }
        }
        // ...
        return result;
    }

Seeing the first if statement, it is exactly the same as what I just said,

li.mOnTouchListener.onTouch(this, event)

1. Since the Touch in the btn event just returned true, here is true, and the three conditions are met, then result = true; the second if statement (false && onTouchEvent) will not execute the statement directly, and the onTouchEvent method will be analyzed later ; so true is returned here. Then it will call its own move, up, and click events, and will not pass them down.
2. If touch returns false, then it will be executed in the second if statement, then it is necessary to judge whether onTouchEvent() returns true to determine the value of result.

3.1 dispatchTouchEvent#onTouchEvent

Code first, then analyze

public boolean onTouchEvent(MotionEvent event) {
    
    
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();  
     //  省略...
     //判断当前view是否有click事件,单击/长按,进入Touch事件
        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)) {
    
    
                                //处理点击事件
                                    perfoTrmClick();
                                }
                            }
                        }

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

  1. First of all, it is to determine whether the view has a click event;
  2. If there is a click event, execute getAction() to determine which gestures are, and eventually return a true;
  3. Returns false if the view has no click event;

If you enter the if statement, when you want to perform gestures such as move, if one of these gestures implements false, it will be intercepted, and the subsequent gestures will not be triggered. Only when all are true, the subsequent gestures will be triggered.
When we implement onTouch monitoring, the gestures are all true returns, and when the View is clicked to slide, it will trigger

ACTION_DOWN->ACTION_MOVE->ACTION_UP

You can see that the perfoTrmClick() method is implemented in ACTION_UP, which handles the click event of the view.
So it is concluded that the click event is executed in up, and up is the last trigger, so the click event is the last trigger in touch monitoring.

3.2 Diagram of dispatchTouchEvent&&onTouchEvent

dispatchTouchEvent execution flow
insert image description here
onTouchEvent() processing flow
insert image description here
This article does not mention View proxy, if you want to learn more, you can refer to other articles.

3.3 Interception of ViewGroup

The above mentioned is all about the process of whether the view event is consumed, now let’s take a look at the process of interception

public class DiyViewGroup extends ViewGroup implements View.OnTouchListener{
    
    
        private Context mContext;
        //拦截事件
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
    
    
            return false;
        }
 }

I have customized the ViweGroup here, rewritten the onInterceptTouchEvent method, and if it returns false, it will not intercept by default and continue to pass the event downward. When true is returned, the various gesture events of the sub-views under the ViweGroup will be invalid because they cannot be received. Delivery of events. At this time, MOVE, UP, DOWN of ViewGroup itself will be called;

Back to nature

back to the original question

  1. The Click event is executed in perfoTrmClick() in the up gesture in onTouchEvent;
  2. OnTouchEvent will determine whether the view has a click event, and the button has its own click event, and the TextView does not implement the click event, so the TextView will return false;
  3. Sliding conflict is not an important knowledge point in this section. As I will talk about later in the blog, to solve the sliding conflict, you need to rewrite the interception event of ViewGrou, get getAction to judge what gesture is, and judge whether the coordinates of the user's movement exceed the sliding area according to the logic. Return true or false.

Finally, thanks to the blogs on the major platforms for providing reference

Guess you like

Origin blog.csdn.net/q1210249579/article/details/110817624