public class MainActivity extends AppCompatActivity {
private Button mButton;
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = findViewById(R.id.btn_click);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "onClick");
}
});
mButton.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e(TAG, "onTouch: " + event.getAction());
return false;
}
});
}
}
当setOnTouchListener 返回为false的时候;
log日志为
先执行onTouch后执行onClick;onClick 在 event.getAction() = 1 后执行。
当setOnTouchListener 返回为true的时候;
log日志为
只执行onTouch方法,onTouch 会有两次回调 event.getAction()有 0 和1 两个值; onClick 在 event.getAction() = 1 之后回调;
MotionEvent.ACTION_UP = 1; MotionEvent.ACTION_DOWN = 0;
那么为什么会这样呢?首先看下View 的setOnClickListener 和 setOnTouchListener方法
View.java
/**
* Register a callback to be invoked when this view is clicked. If this view is not
* clickable, it becomes clickable.
*
* @param l The callback that will run
*
* @see #setClickable(boolean)
*/
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
/**
* Register a callback to be invoked when a touch event is sent to this view.
* @param l the touch listener to attach to this view
*/
public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
都会去初始化一个ListenerInfo对象mListenerInfo,然后将OnClickListener 或者 OnTouchListener 赋值给mListenerInfo的对应属性;
下面方法只是适合我自己用
View代码中搜索mOnTouchListener 方法,可以看到在setOnTouchListener、hasListenersForAccessibility 和 dispatchTouchEvent 方法中有mOnTouchListener
View#hasListenersForAccessibility
/**
* Returns whether the View has registered callbacks which makes it
* important for accessibility.
*
* @return True if the view is actionable for accessibility.
*/
private boolean hasListenersForAccessibility() {
ListenerInfo info = getListenerInfo();
return mTouchDelegate != null || info.mOnKeyListener != null
|| info.mOnTouchListener != null || info.mOnGenericMotionListener != null
|| info.mOnHoverListener != null || info.mOnDragListener != null;
}
该方法将mOnTouchListener 设置为null 可以看出来不是这个方法;
然后来看View#dispatchTouchEvent方法
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;
}
//————————————————————————————代码 1
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;
}
在代码中代码 1位置,获取mListenerInfo对象,该对象在设置两个方法的时候已经初始化; 前两个条件为true,第三个条件是view是否可用,具体代码在View#setEnabled中,第四个条件也就是setOnTouchListener的onTouch返回true 的时候,result = true;下面的逻辑都没有去处理这个result,直接返回了;但是这跟setOnClickListener不执行有什么关系;我们先来看onTouch方法返回false 的时候,这个时候setOnClickListener是执行的。
依旧View中搜索mOnClickListener,在callOnClick、performClick、hasOnClickListeners、setOnClickListener 中含有,可以确定performClick中是OnClickListener.onClick方法的执行
搜索performClick方法(建议加括号搜索),在performClickInternal中调用,搜索performClickInternal 有两处地方调用:
1、类PerformClick 中,PerformClick 是一个Runnble接口的实现类
private final class PerformClick implements Runnable {
@Override
public void run() {
performClickInternal();
}
}
2、在onTouchEvent 的 event.getAction() 是 MotionEvent.ACTION_UP 中,下面代码31行
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
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.
//————————————————代码2
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
}
mIgnoreNextUpEvent = false;
break;
}
return true;
}
return false;
}
来看post(mPerformClick) ,代码2可以看出mPerformClick是PerformClick的实例,也就是条件1中的Runnable的实现类,
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
post方法就是讲Runnable发送到Handler 的消息队列中,或者添加到getRunQueue() 中(保存Runnable, executeActions会handler发送),当消息执行的时候 会执行performClickInternal方法执行mOnClickListener#onClick方法;
也就是说当onTouchEvent方法执行MotionEvent.ACTION_UP的时候去执行mOnClickListener#onClick方法,
在上述分析mOnTouchListener时,当mOnTouchListener#onTouch返回false 下面 会执行onTouchEvent方法
if (!result && onTouchEvent(event)) { result = true; }
当OnTouchListener返回true的时候,就不会进入if语句 ,不会去执行onTouchEvent,也就不会去执行mOnClickListener#onClick方法,返回false 的时候会执行onTouchEvent,也解释了为什么onClick在1 也就是MotionEvent.ACTION_UP之后回调。
看源码的时候是顺着看的,写的时候是倒着写的,View搜索这个只是我自己想的,不一定能用,有的方法/变量会多地方调用,这样看的话会很麻烦!!!有些逻辑就是这么来的,非要找问什么在这个方法里,很很累的
就好比为什么是dispatchTouchEvent中,这就会扯到View事件分发的问题了,有时间在整理事件分发了