PS: In this era of widespread knowledge anxiety, everyone should understand the importance of learning and strive to become a "person who knows how to learn" rather than being the "most diligent fool".
The process of event distribution can be said to have been explained clearly. Before reading this article, please read the following articles:
- Android Event Distribution Basics
- Android Event Distribution Source Code Analysis
- Android Event Distribution Process Analysis
There is also a question about how the onTouch and onClick events are passed during the Android event propagation process. The following mainly introduces the order of calling onTouch, onClick, and the event propagation process from the following aspects:
- onTouch() method in the source code
- onClick() method in the source code
- Relationship between onTouch() and onClick() methods
- Summary
onTouch() method in the source code#
When setting the touch event listener, the OnTouchListener interface in the View class is used, and then the setOnClickListener is used to set the touch event listener. Then, specific operations can be performed based on the specific event type. The onTouch() method is the method defined in the OnTouchListener interface and is called in the dispatchTouchEvent() method of the View. The following is the specific invocation of the onTouch method in the source code:
// Event dispatch
public boolean dispatchTouchEvent(MotionEvent event) {
...
// Default return value
boolean result = false;
...
// Pay attention to the judgment condition
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
ListenerInfo li = mListenerInfo;
// Pay attention to the judgment condition
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;
}
In the above code, let's first look at the outermost condition onFilterTouchEventForSecurity() method. We only need to pay attention to the return value of this method. The source code is as follows:
/**
* Returns true if the event should be dispatched, false if the event should be dropped.
* @see #getFilterTouchesWhenObscured
*/
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
//noinspection RedundantIfStatement
if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
// Window is obscured, drop this touch.
return false;
}
return true;
}
So under normal circumstances, this method returns true. The key conditions are whether ListenerInfo is null, whether mOnTouchListener is null, and the return value of onTouch() method. The following is the source code part of ListenerInfo initialization:
// Specific invocation of getListenerInfo()
public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}
// Initialization of ListenerInfo
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
Obviously, when setting the touch event listener through the setOnTouchListener() method, the ListenerInfo is initialized. At the same time, mOnTouchListener != null is established when setting the touch event listener. Finally, the return value of the onTouch() method determines whether the dispatchTouchEvent() method returns true. Therefore, when a touch listener event is set and the onTouch() method returns true, it means that the event is handled here and will not be passed to the child View. At the same time, the onTouchEvent() method will not be executed if it returns false.
onClick() method in the source code#
When setting the click event listener, the OnClickListener interface in the View class is used, and then the setOnClickListener is used to set the click event listener. Then, specific operations can be performed based on the specific event type. The onClick() method is the method defined in the OnClickListener interface and is called in the onTouchEvent() method of the View. This will be further verified in the following text. The following is the specific invocation of the onClick() method in the source code:
// Event processing
public boolean onTouchEvent(MotionEvent event) {
...
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
...
// If this method is executed, its return value is the return value of onTouchEvent()
performClick();
...
break;
}
return true;
}
return false;
}
In the above code, at least the calling position of the onClick() method is found. The following is the performClick() method:
/**
* Mainly callback OnClickListener
*/
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
// Call the onClick() method in the OnClickListener interface
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
Obviously, as long as we set the click event listener through the setOnClickListener() method in the code, the corresponding View's onTouchEvent() method will return true. Of course, the event will be consumed in this case. Otherwise, it will return false. Then, how is the calling order between onTouch and onClick, and do they affect each other? The relationship between them will be understood from the perspective of examples.
Relationship between onTouch() and onClick() methods#
Still using the previous example, MLinearLayout is nested in MRelativeLayout, and MRelativeLayout is nested in MTextView. All three Views only override the event distribution related to themselves. Then, set the touch event and click event listeners for MTextView. The specific code is as follows:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.textView).setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i("Event", "TextView-------onTouch---------------return:" + false);
return false;
}
});
findViewById(R.id.textView).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i("Event", "TextView-------onClick");
}
});
}
- Let onTouch() return false, and check the log as follows:
Conclusion: When the touch event listener is set, if onTouch() returns false, onTouchEvent() method will be executed after the onTouch() method, and the event will be consumed. Then, a series of events after ACTION_DOWN will be received. In this case, a mouse is used, so there is no ACTION_MOVE event. In the case where onTouch() returns false, onClick() is also executed.
- Let onTouch() return true, and check the log as follows:
Conclusion: When onTouch() returns true, as mentioned earlier, onTouchEvent() will not be executed. Therefore, onClick() will not be executed either.
Summary#
The return value of onTouch() method determines whether onTouchEvent() method should be executed. If onTouch() returns true, onTouchEvent() will not be executed. If it returns false, onTouchEvent() will continue to be executed. The callback of onClick() is called in the onTouchEvent() method, so if onTouchEvent() is not executed, onClick() will not be executed either.