Before analyzing the Android event distribution mechanism, clarify the two basic control types of Android: View and ViewGroup. View is an ordinary control, without sub-layout, such as Button and TextView. ViewGroup inherits from View, indicating that there can be sub-controls, such as Linearlayout and Listview. Today we first understand the event distribution mechanism of View.
Let’s look at the code first. It’s very simple. There is only one Button, and the click events of OnClick and OnTouch are registered with it respectively.
(new () { @Override public void onClick(View v) { ("Tag", "This is button onClick event"); } }); (new () { @Override public boolean onTouch(View v, MotionEvent event) { ("Tag", "This is button onTouch action" + ()); return false; } });
Run the project and the results are as follows:
I/Tag: This is button onTouch action0
I/Tag: This is button onTouch action2
I/Tag: This is button onTouch action2
I/Tag: This is button onTouch action1
I/Tag: This is button onClick event
As you can see, onTouch is executed before onClick, so the order of events is passed onTouch first, and then on OnClick. The specific reason for this is the source code below. At this time, we may have noticed that the method of onTouch has a return value, here is the return false, we change it to true and run it again, the result is as follows:
I/Tag: This is button onTouch action0
I/Tag: This is button onTouch action2
I/Tag: This is button onTouch action2
I/Tag: This is button onTouch action2
I/Tag: This is button onTouch action1
After comparing the results twice, we found that the onClick method is no longer executed. Why is this happening? I will use the source code to clarify this idea step by step.
When viewing the source code, you must first know that all View type control event entrances are dispatchTouchEvent(), so we directly enter the dispatchTouchEvent() method in the View class to take a look.
public boolean dispatchTouchEvent(MotionEvent event) { // If the event should be handled by accessibility focus first. if (()) { // 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. (false); } boolean result = false; if (mInputEventConsistencyVerifier != null) { (event, 0); } final int actionMasked = (); if (actionMasked == MotionEvent.ACTION_DOWN) { // Defensive cleanup for new gesture stopNestedScroll(); } if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && != null && (mViewFlags & ENABLED_MASK) == ENABLED && (this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } } if (!result && mInputEventConsistencyVerifier != null) { (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; }
From line 25 of the source code, we can see that the method of () is executed first. If li != null && != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& (this, event) is true, the result is assigned to true, otherwise the onTouchEvent(event) method will be executed.
From the above, we can see that there are four conditions to meet the requirements.
1. ListenerInfo li, it is a static class in the view, which defines the listening of the event of the view, etc. Therefore, if there are events involving the view, ListenerInfo will be instantiated, so li is not null
2. mOnTouchiListener is assigned in the setOnTouchListener method. As long as the touch event is registered, mOnTouchiListener will not null
3. (mViewFlags & ENABLED_MASK) == ENABLED, which determines whether the currently clicked control is enabled. The button defaults to enable, and this condition is also constant to true.
4. The key point is, (this, event) is the callback control onTouch method. When this condition is also true, result=true, onTouchEvent(event) will not be executed. If onTouch returns false, the onTouchEvent(event) method will be executed again.
We then enter the onTouchEvent method to view the source code.
public boolean onTouchEvent(MotionEvent event) { final float x = (); final float y = (); final int viewFlags = mViewFlags; final int action = (); 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 ((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, ()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now (); } 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(); } = (); = (); postDelayed(mPendingCheckForTap, ()); } else { // Not inside a scrolling container, so show the feedback right away setPressed(true, x, y); checkForLongClick(0); } 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; }
From the 21 lines of the source code, we can see that the control can be clicked and entered into the switch judgment. When we trigger the actual finger to leave, we will enter the case of MotionEvent.ACTION_UP. Let's continue to look down. On the 50 lines of the source code, the mPerformClick() method is called, and we continue to enter the source code of this method to take a look.
public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && != null) { playSoundEffect(); (this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result; }
Now we can see that as long as ListenerInfo and mOnClickListener are not null, the onClick method will be called. As mentioned before, as long as there is a listening event, ListenerInfo is not null. Where is the value assigned with mOnClickListener? Let's continue to look at its source code.
public void setOnClickListener(@Nullable OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; }
Everything is clear when we call the setOnClickListener method to register a click event for the button, we will assign a value to the mOnClickListener. The order of the entire distribution event is onTouch()-->onTouchEvent(event)-->performClick()-->OnClick().
Now we can solve the previous problem.
1. The onTouch method takes precedence over OnClick, so onTouch is executed and onClick is executed.
2. Whether it is dispatchTouchEvent or onTouchEvent, if true is returned, it means that the event has been consumed and processed and will not be uploaded down. You can see in the source code of the dispatchTouchEvent that if onTouchEvent returns true, then it also returns true. If onTouch returns true when the dispatchTouchEvent is executed onTouch listening, then it also returns true, and this event is consumed by onTouch in advance. Then you no longer execute onTouchEvent, let alone onClick listening.
The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.