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的事件分发机制。
如下是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方法。
如果Button同时设置了OnTouchListener和OnClickListener会怎么样呢?从刚刚分析的dispatchTouchEvent方法可以知道,如果onTouch返回true就会消费掉手指抬起的事件,进而跳过onClick方法。onTouch返回false,OnClickListener才会收到这个事件。
那么能不能既让onTouch返回true,又让onClick调用呢?其实是可以的。
public boolean performClick() {
...
}
仔细一看会发现performClick是public方法。刚刚分析过performClick最终会调用onClick方法。在onTouch返回true之前调用它就行了。
在刚学Android的时候,相信很多朋友都会有这么一个问题。Android开源框架一抓一大把,用起来非常方便,为什么还要去读源码,遇到问题直接百度不就行了?
红色的L型零件就像是框架,要是L型或一字型零件迟迟不来呢?不是说不能百度谷歌,只是不应该太依赖它们。