setOnClickListener 和 setOnTouchListener 执行问题

2,357 阅读4分钟
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日志为

clipboard.png

先执行onTouch后执行onClick;onClick 在 event.getAction() = 1 后执行。

当setOnTouchListener 返回为true的时候;

log日志为

clipboard111.png

只执行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事件分发的问题了,有时间在整理事件分发了