An article to get "Android event distribution"

What is event distribution

Event distribution is a mechanism for distributing screen touch information to the control tree. When we touch the screen, a series of MotionEvent event objects will be generated, which will be dispatched by calling the dispatchPointerEvnet method of the view through ViewRootImpl, the manager of the control tree.
In-depth study of the event distribution mechanism is to prepare for solving the sliding conflict problem encountered in Android development. The event distribution mechanism describes how a series of events of the user's gestures are delivered and consumed by the Android system.

MotionEvent event

We can see these events in MotionEvent.java. (There are many events, here are just a few important and commonly used ones)

event effect
ACTION_DOWN Triggered when the first finger touches the screen for the first time
ACTION_MOVE Triggered when the finger slides on the screen, it will be triggered multiple times
ACTION_UP Triggered when the last finger leaves the screen
ACTION_CANCEL The event is canceled or overwritten (the event is intercepted by the upper layer and sent by the parent View, not triggered by the user himself), that is, it is not triggered artificially
ACTION_POINTER_DOWN There is a non-primary finger pressed (i.e. a finger was already on the screen before the press)
ACTION_POINTER_UP There is a non-primary finger lift (i.e. there is still a finger on the screen after lifting)

How events go from screen to app

Although this part does not need to be entangled for the time being, you still need to know the whole process. For the time being, focus on the event development of the page. Later, if you want to go deeper, you can conduct in-depth research.

InputManagerService

First of all, after the Android system is started, a series of system services will be started in the SystemServer process, such as AMS, WMS, etc.
One of them is the InputManagerService (IMS) that we manage event input.
InputManagerService: This service is used to communicate with the hardware and accept screen input events. It will read the hardware point InputDispatcher thread received by our system, and then perform unified event distribution and scheduling.

WindowManagerService

The drawing and event distribution of views in Android are based on the view tree. Each view tree is a window.
Each view tree has a root called ViewRootImpl, which is responsible for managing the drawing and event distribution of the entire view tree.
1. So we must eventually notify the ViewRootImpl of the View tree of the event.
2. It is our WindowManagerService (WMS) that manages the communication with the Window.
3. Each viewRootImpl has a corresponding windowState in the wms, and the wms can find the corresponding viewRootImpl through the windowState for management.
4. WMS provides Binder communication through windowState to provide relevant required Window information, which is sent to APP related events by IMS.
5. InputEventReceiver is responsible for receiving input events and sends them to ViewRootImpl class for processing through Handler.
6. ViewRootImpl has an internal class ViewRootHandler, which Inherited from Handler, it works on the main thread and is mainly used to handle various input events, such as touch events, key events, etc.
7. Finally sent to Activity

Window

1. The window mechanism is to manage the display of views on the screen and the transmission of touch events.
2. What is a window? In the Android window mechanism, each view tree can be regarded as a window.
3. What is a view tree? For example, if you set a layout xml for the Activity in the layout, then the topmost layout such as LinearLayout is the root of the view tree, and all the views it contains are the nodes of the view tree, so the view tree corresponds to a window.

Each view tree corresponds to a window, the view tree is the existence form of the window, and the window is the carrier of the view tree. The application interface, dialog, popupWindow and the floating window described above are all manifestations of the window.
To give a few specific examples:

  • When we add a dialog, we need to set a view for it, then this view does not belong to the layout of the activity, it is added to the screen through the WindowManager, and does not belong to the view tree of the activity, so this dialog is an independent view tree, so he is a window.
  • popupWindow also corresponds to a window, because it is also added through the windowManager and does not belong to the view tree of the Activity.
  • When we use windowManager, any view added on the screen does not belong to the layout view tree of the Activity, even if only a button is added.
    An important reason to understand the window mechanism is that event distribution is not driven by Activity, but by the system service driver viewRootImpl for distribution. It can even be said that it has nothing to do with Activity from the perspective of the framework layer. This will help us understand the nature of event distribution.

summary

1. When our fingers touch the screen, a touch point information will be generated, including position, pressure and other information. This touch information is generated by the hardware of the screen, obtained by the underlying driver of the system, and handed over to Android's input system service: InputManagerService, which is IMS.
2. The input system will call the API of the window manager (WMS) to determine which Window and corresponding View the touch event should be distributed to. That is to say, WMS provides View information.
3. The IMS gets the information provided by the WMS and sends it to the ViewRootImpl corresponding to the View. Here, InputChannel is helping to establish SocketPair for two-way communication. If you are interested, you can check the relevant content of InputChannel, and I won’t explain it here.

insert image description here

How the event reaches the corresponding page from the APP

Step 1: Classification

Then the event has now arrived. InputEventReceiver notified ViewRootImpl to see what he did.
Here are a few important methods:

//ViewRootImpl.java ::WindowInputEventReceiver
//1、接收到事件
final class WindowInputEventReceiver extends InputEventReceiver {
    
    
    public void onInputEvent(InputEvent event) {
    
    
        enqueueInputEvent(event, this, 0, true);
    }
}

//ViewRootImpl.java
//2、简单处理掉用doProcessInputEvents进行分类
void enqueueInputEvent(InputEvent event,
                       InputEventReceiver receiver, int flags, boolean processImmediately) {
    
    
    adjustInputEventForCompatibility(event);
    .....
    .....
    .....
    if (processImmediately) {
    
    
        doProcessInputEvents();
    } else {
    
    
        scheduleProcessInputEvents();
    }
}

//3、维护了输入事件队列
void doProcessInputEvents() {
    
    
   .....
    while (mPendingInputEventHead != null) {
    
    
        QueuedInputEvent q = mPendingInputEventHead;
        mPendingInputEventHead = q.mNext;
        deliverInputEvent(q);
    }
    ......
}

//调用InputStage责任链处理分类
private void deliverInputEvent(QueuedInputEvent q) {
    
    
    InputStage stage;
    ....
    //stage赋值操作
    ....
    if (stage != null) {
    
    
        stage.deliver(q);
    } else {
    
    
        //事件分发完成后会调用finishInputEvent,告知SystemServer进程的InputDispatcher线程,
        //最终将该事件移除,完成此次事件的分发消费。
        finishInputEvent(q);
    }
}

abstract class InputStage {
    
    
    .....
    public final void deliver(QueuedInputEvent q) {
    
    
        if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
    
    
            forward(q);
        } else if (shouldDropInputEvent(q)) {
    
    
            finish(q, false);
        } else {
    
    
            traceEvent(q, Trace.TRACE_TAG_VIEW);
            final int result;
            try {
    
    
                result = onProcess(q);
            } finally {
    
    
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
            apply(q, result);
        }
    }
}

It can be seen here that ViewRootImpl further classifies time such as view input events, input method events, navigation panel events, and so on. So where is the InputStage chain of responsibility generated specifically, and what types are there?
In the setView method we can get the answer

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    
    
    synchronized (this) {
    
    
       ...
        mSyntheticInputStage = new SyntheticInputStage();
        InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
        InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
                "aq:native-post-ime:" + counterSuffix);
        InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
        InputStage imeStage = new ImeInputStage(earlyPostImeStage,
                "aq:ime:" + counterSuffix);
        InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
        InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
                "aq:native-pre-ime:" + counterSuffix);
     ....
    }
}

It can be seen that in the setView method, the chain of responsibility for this input event processing is completed.

chain of responsibility member effect
SyntheticInputStage Handle events such as navigation panels, joysticks, etc.
ViewPostImeInputStage The view input processing stage, such as keystrokes, finger touch and other motion events, our common view event distribution occurs at this stage
NativePostImeInputStage In the local method processing stage, a deferrable queue is mainly constructed
EarlyPostImeInputStage Input method early processing stage
ImeInputStage Input method event processing stage, processing input method characters
ViewPreImeInputStage View preprocessing input method event phase
NativePreImeInputStage Native method preprocessing input method event phase

So the first step is to classify and distribute our events through InputStage. Our View touch event occurs in the ViewPostImeInputStage stage. So we classify the event to ViewPostImeInputStage Oh, brothers.

Step 2: Send to Activity

Our View hierarchy is as follows. Activity -> PhoneWindow -> DecorView
Usually when we call setContentView to draw the layout, it is also drawn in the ContentView of DecorView.
insert image description here
Then we have to see how the event is passed to the beginning Activty of the page event.
First of all, as we said above, all events are handled in the ViewPostImeInputStage chain of responsibility.
Let's take a look at what ViewPostImeInputStage has, my dears.

//ViewRootImpl.java ::ViewPostImeInputStage 
final class ViewPostImeInputStage extends InputStage {
    
    
    .....
    .....
    private int processPointerEvent(QueuedInputEvent q) {
    
    
        final MotionEvent event = (MotionEvent)q.mEvent;
        boolean handled = mView.dispatchPointerEvent(event)
        return handled ? FINISH_HANDLED : FORWARD;
    }

    //View.java
    public final boolean dispatchPointerEvent(MotionEvent event) {
    
    
        if (event.isTouchEvent()) {
    
    
            return dispatchTouchEvent(event);
        } else {
    
    
            return dispatchGenericMotionEvent(event);
        }
    }

It can be seen that in the end, we still came to mView.dispatchPointerEvent(event)
and the mView in ViewRootImpl is DecorView.
Now the event has been passed to DecorView, which is the root layout of our interface.

//DecorView.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    
    
    //Callback是我们的Activity和Dialog
    final Window.Callback cb = mWindow.getCallback();
    return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
            ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}

The cb above is the Activity we created when we created the layout.
After that, we can find our dispatchTouchEvent in the Activity. Everyone is familiar with this.
It is the big traitor DecorView that passes the time to the Activity through getCallback, making the Activity the beginning of the page event.

subsequent delivery

Let's take a look at dispatchTouchEvent and subsequent delivery in Activity

//Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
    
    
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    
    
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
    
    
        return true;
    }
    return onTouchEvent(ev);
}

//PhoneWindow.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    
    
    return mDecor.superDispatchTouchEvent(event);
}

//DecorView.java
public boolean superDispatchTouchEvent(MotionEvent event) {
    
    
    return super.dispatchTouchEvent(event);
}

We found that in the end , the whole process was passed back to DecorView as ViewRootImpl -> DecorView -> Activity -> PhoneWindow -> DecorView

Question: So the question is why doesn't ViewRootImpl start processing events directly from Activity?
Answer: ViewRootImpl does not know that there is such a thing as Activity! It just holds the DecorView. Therefore, touch events cannot be sent directly to Activity.dispatchTouchEvent()

Question: So what to do when there is no cb, that is, there is no Activity. We see that else calls super.dispatchTouchEvent(ev);
answer: So if the top-level viewGroup is not DecorView, then dispatch the dispatchTouchEvent method of the corresponding view. For example, if the top-level view is a Button, the dispatchTouchEvent method of Button will be called directly; if the top-level viewGroup subclass does not override the dispatchTouchEvent method, then the default dispatchTouchEvent method of ViewGroup will be called directly.

Question: Why doesn't Activity call DecorView directly
? Answer: Because Activity doesn't maintain DecorView, which is maintained by PhoneWindow.

summary:

1. First, the event is received by InputEventReceiver and sent to ViewRootImpl for processing, and then classified first.
2. ViewRootImpl , as
the processing class of
View , is responsible for event processing and management of View. 3. Events are classified into ViewPostImeInputStage and passed to dispatchTouchEvent of mView
. 4. mView is DecorView root layout of the layout
5. Through ViewRootImpl -> DecorView -> Activity -> PhoneWindow -> DecorView and finally to the event distribution of the page ViewGroup
6. PhoneWindow will have a simple explanation at the end

Page event distribution

DecorView inherits from FrameLayout, but FrameLayout does not rewrite the dispatchTouchEvent method, so the method of the ViewGroup class is called. So here, the event is handed over to ViewGroup to distribute to the control tree.
Of course, our ViewGroup contains a large number of our Views.

the entire process

Combining the above process, we can know that the whole process is:
ViewRootImpl -> DecorView -> Activity ->PhoneWindow -> DecorView -> ViewGroup -> View
However, the last thing we focus on in application development is to start from Activity, or from the event processing of ViewGroup .
The following will take Activity as an example to explain

Starting from the Activity

Activity -> PhoneWindow -> DecorView -> ViewGroup -> View
Among them, we don't need to deal with PhoneWindow -> DecorView when we are on the page, so our process can be simplified to
Activity -> ViewGroup -> View
. Is it familiar at once? The old-fashioned Activity -> ViewGroup -> View

sequence of events

Refers to a series of events generated during the process from the moment the finger first touches the screen to the moment the finger leaves the screen. This sequence generally starts with a down event, contains multiple move events in the middle, and finally ends with an up event

Source code analysis

Here is a simple analysis of the source code. It is not difficult to read it, and there is not much code. It is mainly to understand the important methods in the process.

Activity's handling of events

//Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
    
      
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    
      
            onUserInteraction();  
        }  
        if (getWindow().superDispatchTouchEvent(ev)) {
    
      
            return true;  
        }  
        return onTouchEvent(ev);  
}

It can be seen from the source code that the event is handed over to the Window attached to the Activity for distribution. Returning true will end the event distribution. Otherwise, the onTouchEvent of all Views will return false (none of them will be processed). At this time, it is the onTouchEvent of the Activity. deal with.

Window's handling of events

The only implementation class of Window is PhoneWindow

//PhoneWindow.java
@Override  
public boolean superDispatchTouchEvent(MotionEvent event) {
    
      
    return mDecor.superDispatchTouchEvent(event);  
}

As can be seen from the source code, the event is handed over to DecorView for processing. Let's continue to look at DecorView.

DecorView's handling of events

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker{
    
    }
 
public boolean superDispatchTouchEvent(MotionEvent event) {
    
    
    return super.dispatchTouchEvent(event);
}

DecorView is inherited from FrameLayout, and everyone knows that FrameLayout inherits ViewGroup, so the next level is ViewGroup.

ViewGroup's handling of events

The core code of dispatchTouchEvent is as follows:
Compared with Activity and View, ViewGroup has an additional onInterceptTouchEvent() event interception method, and the event is passed to ViewGroup. If onInterceptTouchEvent returns true, the event will be processed by ViewGroup. If false is returned, the dispatchTouchEvent of the child View will be called

// 检查是否进行事件拦截  
final boolean intercepted;  
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
    
      
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
    if (!disallowIntercept) {
    
      
    //回调onInterceptTouchEvent(),返回false表示不拦截touch,否则拦截touch事件 
    intercepted = onInterceptTouchEvent(ev);  
    ev.setAction(action);
  } else {
    
      
      intercepted = false;  
     }  
} else {
    
      
   //没有touch事件的传递对象,同时该动作不是初始动作down,ViewGroup继续拦截事件  
   intercepted = true;
}

When ViewGroup's onInterceptTouchEvent returns false, it will first traverse all child elements to determine whether the child elements can receive click events. If the child element has the conditions to receive the event, its dispatchTouchEvent will be called. If all the child elements are traversed and false is returned, then the ViewGroup can only handle the event by itself. Returning true from this method of a child element will stop traversing the child element.

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

View's handling of events

Then the event of the last View is processed and
the View cannot continue to pass down the event. After a series of judgments, the event is finally consumed through onTouchEvent

public boolean dispatchTouchEvent(MotionEvent event) {
    
      
boolean result = false;
//...
    if (onFilterTouchEventForSecurity(event)) {
    
      
       //noinspection SimplifiableIfStatement  
       ListenerInfo li = mListenerInfo;  
       //会执行View的OnTouchListener.onTouch这个函数,若返回true,onTouchEvent便不会再被调用了。
       //可见OnTouchListener比onTouchEvent优先级更高。
       if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED  
                    && li.mOnTouchListener.onTouch(this, event)) {
    
      
         return true; }  
  
       if (onTouchEvent(event)) {
    
      
            return true; 
       }  
}
    //…
    return result;  
}

Little knowledge: the judgment also uses li.mOnTouchListener.onTouch(this, event). So OnTouchListener has higher priority than onTouchEvent. It is also reflected here.

Page Distribution Process

Don't talk nonsense and go straight to the U-shaped flow chart. The clichéd flow charts are all used badly.
insert image description hereNote: Don't be misled by the picture, there is no onTouchEvent in ViewGroup. Because ViewGroup inherits View, the onTouchEvent of ViewGroup is in View.

dispatchTouchEvent() : Distribute click events

The dispatchTouchEvent() method is the core method of event distribution, and it is a method implemented by Activity, ViewGroup, and View. In event distribution, the event will first be passed to the dispatchTouchEvent() method of the Activity, which will pass the event to the dispatchTouchEvent() method of the parent container and child View for processing according to the specific situation. If the event is consumed, the event delivery will be stopped immediately, otherwise it will be delivered to the last View. Returns true if the View handled the event, otherwise returns false.

onInterceptTouchEvent(): Intercept click events at the ViewGroup layer

The onInterceptTouchEvent() method is a method in ViewGroup. Its main function is to intercept the TouchEvent of the sub-View in the ViewGroup, that is, intercept the TouchEvent of the sub-View. If ViewGroup intercepts TouchEvent, the corresponding child View cannot receive TouchEvent.

onTouchEvent(): Handle click events

The onTouchEvent() method is a method of View, and its main function is to handle the TouchEvent of View, such as View being clicked, View being dragged, and so on. When View receives a TouchEvent, it will respond to the TouchEvent through the onTouchEvent() method. Returns true if the View handled the event, otherwise returns false.

summary

The summary is based on the picture, you need to compare it with the picture (be sure to look at the picture, otherwise you will not want to read the words) 1.
First, the consumption means that the time is up to this point and will not continue to pass.
2. If we do not check the control method to rewrite or change the return value, and directly use super to call the default implementation of the parent class, then the entire event flow should be from Activity---->ViewGroup->View to call the dispatchTouchEvent method from top to bottom, all the way to the leaf node (View), then call the onTouchEvent method from bottom to top by View—>ViewGroup—>Activity.
3. For dispatchTouchEvent, onTouchEvent, return true is the end of event delivery. return false is backtracking to the onTouchEvent method of the parent View.
5. onTouchEvent return false means not to consume the event, and let the event continue to flow from bottom to top in the direction of the parent control.
6. If ViewGroup wants to distribute itself to its own onTouchEvent, it needs the interceptor onInterceptTouchEvent method return true to intercept the event.
7. View has no interceptor. In order to allow View to distribute events to its own onTouchEvent, the default implementation (super) of View's dispatchTouchEvent is to distribute events to its own onTouchEvent.

ACTION_MOVE 和 ACTION_UP

For the situation of consuming events in onTouchEvent: in which View's onTouchEvent returns true, then the ACTION_MOVE and ACTION_UP events will no longer be passed down after being passed down to this View, but directly passed to its own onTouchEvent and end this time Event delivery process.
For example:
we return true in ViewGroup2's onInterceptTouchEvent to intercept this event and return true in ViewGroup 1's onTouchEvent to consume this event.
The red arrow represents the flow direction of the ACTION_DOWN event. The
blue arrow represents the flow direction of the ACTION_MOVE and ACTION_UP events.
insert image description here
You can see that our ACTION_MOVE and ACTION_UP will no longer be passed down in the ViewGroup1 where the event is consumed, and will be returned directly to the onTouchEvent of ViewGroup1 for processing. .

Explanation of Cancel event

When will the ACTION_CANCEL event be triggered?
First, the Cancel event is notified by the parent View to the child View.
There are two ways to trigger the Cancel event:
1. The ViewGroup intercepts and consumes the event.
2. The View is removed from the ViewGroup.

In the above two cases, it must be ensured that the Down event is when the View needs to be consumed.
Give two examples:
Example 1: ViewGroup intercepts and consumes events

In ScrollView, the priority of gestures is generally scrolling > clicking.
In a scrollable control like Scrollview, if the finger continues to slide after pressing the operation, the previous Child controls in the point send a Cancel event

This is because when the finger is pressed, if the user continues to slide up or down, the scrolling operation of the ScrollView will be triggered, and the ScrollView will also respond to the click event of the child control. If the user continues to swipe up or down at this point, there will be a conflict: the ScrollView wants to scroll, and the child control also wants to handle the click event.

In order to avoid this conflict, ScrollView will send a Cancel event to the previously clicked child control when the user presses and continues to slide, informing it to cancel the previous click operation. In this way, the ScrollView can scroll smoothly, and the child controls will not mishandle.

That is to say, the ScrollView sub-View is intercepted by the ScrollView ViewGroup. In the source code of ScrollView we can see

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    
    
    if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
    
    
        return true;
    }

    if (super.onInterceptTouchEvent(ev)) {
    
    
        return true;
    }
    ....
    ....
    switch (action & MotionEvent.ACTION_MASK) {
    
    
        case MotionEvent.ACTION_MOVE: {
    
    
            if (yDiff > mTouchSlop && (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
    
    
                mIsBeingDragged = true;
                mLastMotionY = y;
                initVelocityTrackerIfNotExists();
                mVelocityTracker.addMovement(ev);
                mNestedYOffset = 0;
                if (mScrollStrictSpan == null) {
    
    
                    mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
                }
                final ViewParent parent = getParent();
                if (parent != null) {
    
    
                    parent.requestDisallowInterceptTouchEvent(true);
                }
            }
            break;
        }
        ....
        ....    
    }
    return mIsBeingDragged;
}

In the process of ACTION_MOVE, look at the If judgment, let me interpret it.

If the distance the finger moves in the vertical direction is greater than the system default minimum touch distance (mTouchSlop), and there is no vertical scrolling on the nested sliding axis (NestedScrolling), mark the ScrollView as being dragged (mIsBeingDragged = true) .
Record the current finger position (mLastMotionY = y), and initialize the velocity tracker (initVelocityTrackerIfNotExists()) to track the user's swipe velocity. In addition, set NestedYOffset to 0 to prepare for handling nested slides.

That is to say, mIsBeingDragged = true after ACTION_MOVE is determined to be in a sliding state. That is, the return value of onInterceptTouchEvent is true.
We mentioned above that if onInterceptTouchEvent returns true, the event will be intercepted in the ViewGroup of this layer and sent to the onTouchEvent of the current ViewGroup for consumption. That is, the event is consumed to ScrollView.

Summary: Because ScrollView intercepts events when sliding, it sends a Cancel event to the child View

Example 2: View is removed from ViewGroup.
It is relatively simple for you to test it yourself:

press your finger on View, and remove View from ViewGroup after 3 seconds. Then observe the event of onTouchEvent to return.

viewGroup.postDelayed(new Runnable() {
    
    
        @Override
        public void run() {
    
    
            getWindowManager().removeView(getWindow().getDecorView());
        }
    }, 3000);

Common Event Distribution Issues

nested slide

When multiple sliding controls exist at the same time, sliding events may interfere with each other, and an event distribution mechanism is required to solve the problem of sliding conflicts.
I will write a separate article on nested sliding to explain it, with some practical use cases.
For example:
1. ScrollView + ListView (RecyclerView) nesting conflict
2. ScrollView + ViewPager nesting problem
3. RecyclerView + RecyclerView nested sliding in the same and different directions
4. The common homepage of the mall APP has multiple lists, and there are tabs on the top

multi touch

Because multi-touch may cause interlaced fingers, resulting in chaotic event distribution, it is necessary to handle multi-touch events through an event distribution mechanism.

The relationship between onTouch, onTouchEvent, onLongClick, onClick

1. The priority of onTouch is higher than that of onTouchEvent . The source code explained above in View's processing of events, because View's dispatchTouchEvent is judged by onTouchListner.onTouch(). When the View's OnTouchListener.onTouch function returns true, onTouchEvent will not be called again. Of course, onClick and Long events will no longer respond.
2. OnClick has the lowest priority : onClick is in the method of onTouchEvent. The following code is evidence. You can see that in MotionEvent.ACTION_UP , the click is executed by performClick()
in the onTouchEvent of View.
The event is actually setting our onClickListens

case MotionEvent.ACTION_UP:
        .....
        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
    
    
            // This is a tap, so remove the longpress check
            removeLongPressCallback();
            if (!focusTaken) {
    
    
                .....
                if (mPerformClick == null) {
    
    
                    mPerformClick = new PerformClick();
                }
                if (!post(mPerformClick)) {
    
    
                    performClickInternal();
                }
            }
        }

Click into performClick() which can be traced back.
We can see li.mOnClickListener.onClick(this); to trigger the onClick event
, so onTouchEvent takes precedence over onClick()

 public boolean performClick() {
    
    
        notifyAutofillManagerOnClick();
        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);
        notifyEnterOrExitForAutoFillIfNeeded(true);
        return result;
    }

3. onLongClick > onClick . Everyone knows that onClick will be blocked when onLongClick returns true. So what's going on, look at the source code:
The trigger of LongClick starts from ACTION_DOWN and is completed by the CheckForLongClick() method. The
onClick() method is called back in the performClick() method of View. The
onLongClick of onLongClickListener is called back in the performLongClickInternal method of View. ()method

private final class CheckForLongPress implements Runnable {
    
    
        private int mOriginalWindowAttachCount;
        private float mX;
        private float mY;
        private boolean mOriginalPressedState;

        @Override
        public void run() {
    
    
            if ((mOriginalPressedState == isPressed()) && (mParent != null)
                    && mOriginalWindowAttachCount == mWindowAttachCount) {
    
    
                if (performLongClick(mX, mY)) {
    
    
                    mHasPerformedLongPress = true;
                }
            }
        }
        ...
    }

It can be seen that when performLongClick returns true, mHasPerformedLongPress = true; careful students may find that I have seen mHasPerformedLongPress. That's right!
It is in the if condition of calling performClick() in the onTouchEvent of the view seen above.

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) {
    
    
        if (!post(mPerformClick)) {
    
    
            performClick();
        }
    }
}

That is to say, performLongClick returns true. If it doesn't work, performClick() will not be called, and naturally onClick cannot be called.
4. Summary: onTouch > onTuchEvent > onLongClick > onClick

The impact of Enable on View event distribution

When we set Enable = false to View, then View will not respond to our events. That is to say, none of the four events mentioned above will be responded to, including onTouch.
So how are events handled? How was it affected?
Go directly to the source code! ! ! !

public boolean dispatchTouchEvent(MotionEvent event) {
    
    
		.........
		.........
        if (onFilterTouchEventForSecurity(event)) {
    
    
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
    
    
                result = true;
            }
           
            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;
            }
        }

see it? Babies, when (mViewFlags & ENABLED_MASK) == ENABLED = false, you will not go to li.mOnTouchListener.onTouch(this, event) anymore.
The onTouch boss is not called anymore. Later, the younger brother called a fart.

Here is a question for everyone: Then when Enable = false is set for View. Where are events consumed?
Save the homework, folks.

Summarize

There is no summary, I hope you can read it carefully. A lot of content, you can read in batches. About nested sliding will be explained in the next article.
If you find any problems, please comment below, let's make progress together.

Guess you like

Origin blog.csdn.net/weixin_45112340/article/details/130863139