setOnClickListener 和 setOnTouchListener

mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
				...
            }
        });

这是OnClickListener常见的使用方法,跳进源码看看。

//View.java
public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
        	//1
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
}

跳进去会发现setOnClickListener方法并不是Button特有的,其实在它的父类View中就已经实现这个方法了。如果之前把此控件的CLICKABLE标记清除了(如在xml文件上设置android:clickable=“false”),那就得先在代码1上把这个标记重新标上。

接着获取这个控件的ListenerInfo,绑定传入的OnClickListener对象。那么ListenerInfo又是什么呢?跳进它的java文件看看。

static class ListenerInfo {
	...
	public OnClickListener mOnClickListener;
	protected OnLongClickListener mOnLongClickListener;
	private OnTouchListener mOnTouchListener;
	...
}

这是一个静态类,没有方法,只有成员变量,其中有点击监听器、长按监听器和触摸监听器。这么说来,ListenerInfo 的作用其实就是用来存储各种外部事件监听器的。再来看看setOnTouchListener方法。

mButton.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if(...){
                	...
                	return true;
                }
                ...
                return false;
            }
});

基本用法是这样的,onTouch返回true就会消费掉这个事件,返回false则不会。看看setOnTouchListener方法做了什么。

public void setOnTouchListener(OnTouchListener l) {
        getListenerInfo().mOnTouchListener = l;
}

比setOnClickListener还简单,绑定OnTouchListener就完成了。

监听事件

前面我们只是设置了事件监听器,那么Android应用是怎么监听和处理事件的呢?由于不同控件对事件的的处理是不同的,这里就以Button为例。

在开始之前,首先需要简单讲讲Android的事件分发机制。

android没有setOnClickListener提示 安卓setonclicklistener_Click


如下是3个重要的方法。

1.dispatchTouchEvent方法,用于分发事件。

2.interceptTouchEvent方法,用于拦截事件。

3.interceptTouchEvent方法,用于处理事件。

Activity是最高级的,View是最低级的。某一级觉得可以把事件分配给下级,dispatchTouchEvent就调用它的父方法,想自己消费掉就返回true,自己无法处理事件就返回false让上级来处理。也就是说事件分配顺序是Activity—>ViewGroup—>View

而事件处理顺序是View—>ViewGroup—>Activity。这是对事件分发机制的简单说明,对其还不了解的朋友可以先看看这篇文章

回到刚刚那个图片,看到绿色的部分。这个时候事件就已经分发到Button手上了,看看源码。

//View.java
public boolean dispatchTouchEvent(MotionEvent event) {
	...
	//1
	final int actionMasked = event.getActionMasked();
	...
	if (onFilterTouchEventForSecurity(event)) {
            ...
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            //2
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

			//3
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        ...
        return result;
}

Button并没有重写dispatchTouchEvent方法,而是使用View的实现方法。首先在代码1获取事件类型。

代码2和代码3的判断这样分析。如果没有设置了OnTouchListener,dispatchTouchEvent方法返回的结果就是OnTouchListener.onTouch方法的结果。

如果没有设置OnTouchListener或者OnTouchListener.onTouch返回了false,那就调用onTouchEvent方法,看看它做了什么。

public boolean onTouchEvent(MotionEvent event) {
	...
	//1
	final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
    ...
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                //2
                case MotionEvent.ACTION_UP:
                	...
                	//3
                	performClickInternal();
             		...
             }
}

首先在代码1看看Button是否可以接受点击事件。代码2的case就是手指抬起时的事件,到了代码3开始处理点击事件,继续深入最后在performClick方法找到了OnClickListener的回调方法。

public boolean performClick() {
        ...
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
        	//1
            playSoundEffect(SoundEffectConstants.CLICK);
            //2
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
        ...
        return result;
    }

先调用playSoundEffect方法播放点击按钮的音效,再回调OnClickListener的onClick方法。

onTouch方法 和 onClick方法 的关系

如果Button同时设置了OnTouchListener和OnClickListener会怎么样呢?从刚刚分析的dispatchTouchEvent方法可以知道,如果onTouch返回true就会消费掉手指抬起的事件,进而跳过onClick方法。onTouch返回false,OnClickListener才会收到这个事件。

那么能不能既让onTouch返回true,又让onClick调用呢?其实是可以的。

public boolean performClick() {
	...
}

仔细一看会发现performClick是public方法。刚刚分析过performClick最终会调用onClick方法。在onTouch返回true之前调用它就行了。

最后

下一篇分析OnLongClickListener的原理及三者的优先级。

题外话

在刚学Android的时候,相信很多朋友都会有这么一个问题。Android开源框架一抓一大把,用起来非常方便,为什么还要去读源码,遇到问题直接百度不就行了?

android没有setOnClickListener提示 安卓setonclicklistener_控件_02


红色的L型零件就像是框架,要是L型或一字型零件迟迟不来呢?不是说不能百度谷歌,只是不应该太依赖它们。