上一篇对 OnClickListener 和 OnTouchListener 的源码进行了分析。本篇文章讲解OnLongClickListener的实现原理。
源码分析
下面是 OnLongClickListener 的基本使用方法。
button.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
...
return true;
}
});
返回true或false有什么区别吗?后面会分析,先进去 setOnLongClickListener 方法看看做了什么。
public void setOnLongClickListener(@Nullable OnLongClickListener l) {
if (!isLongClickable()) {
setLongClickable(true);
}
getListenerInfo().mOnLongClickListener = l;
}
代码很简单,先检查控件是否可以长按,长按功能被关闭的话就打开。然后把长按监听器存进 ListenerInfo 里。来回顾一下 ListenerInfo。
static class ListenerInfo {
...
@UnsupportedAppUsage
public OnClickListener mOnClickListener;
@UnsupportedAppUsage
protected OnLongClickListener mOnLongClickListener;
@UnsupportedAppUsage
private OnTouchListener mOnTouchListener;
...
}
ListenerInfo是View的一个静态内部类,它的作用就是用来保存View的各种监听器的,不仅是OnLongClickListener,OnClickListener、OnTouchListener等常用的监听器也是存放在这里面的。
分析到这里,设置 OnLongClickListener 的过程就结束了,那么控件是在什么时候使用它的呢?既然是触摸事件,那不妨到 onTouchEvent 方法看看。
长按事件可以分解为 按下 和 抬起 两个步骤,先在ACTION_DOWN事件里找找。
public boolean onTouchEvent(MotionEvent event) {
...
case MotionEvent.ACTION_DOWN:
...
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
break;
}
...
}
mHasPerformedLongPress用于标记是否已经长按。
ViewConfiguration.getLongPressTimeout 方法可以获取到长按触发所需的时间,默认是500ms。
/**
* Defines the default duration in milliseconds before a press turns into
* a long press
*/
private static final int DEFAULT_LONG_PRESS_TIMEOUT = 500;
x,y是指长按的坐标,我们再进去 checkForLongClick 方法看看。
private void checkForLongClick(long delay, float x, float y, int classification) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
mPendingCheckForLongPress.rememberPressedState();
mPendingCheckForLongPress.setClassification(classification);
//1
postDelayed(mPendingCheckForLongPress, delay);
}
}
完成了相关的设置后把 mPendingCheckForLongPress延迟一段时间(delay)再发送出去,到这里应该也能猜到是用Handler了,不妨进去 postDelayed 方法里看看。
public boolean postDelayed(Runnable action, long delayMillis) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.postDelayed(action, delayMillis);
}
...
}
果然,用到了Handler,手指按下后延时500ms回调 mPendingCheckForLongPress,也就是说在 mPendingCheckForLongPress 完成了长按事件的回调,进去看看。
private final class CheckForLongPress implements Runnable {
...
@Override
public void run() {
if ((mOriginalPressedState == isPressed()) && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
recordGestureClassification(mClassification);
//1
if (performLongClick(mX, mY)) {
//2
mHasPerformedLongPress = true;
}
}
}
...
}
如果 performLongClick 方法返回true则长按事件被消耗掉,进去看看。
public boolean performLongClick(float x, float y) {
mLongClickX = x;
mLongClickY = y;
final boolean handled = performLongClick();
mLongClickX = Float.NaN;
mLongClickY = Float.NaN;
return handled;
}
public boolean performLongClick() {
return performLongClickInternal(mLongClickX, mLongClickY);
}
private boolean performLongClickInternal(float x, float y) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLongClickListener != null) {
//1
handled = li.mOnLongClickListener.onLongClick(View.this);
}
...
return handled;
}
代码1就是我们设置的长按监听真正回调的地方了,从这也可以看出返回的布尔量就是指这个长按事件是否被消耗。消耗长按事件 mHasPerformedLongPress 就会被置为 true。
接下来再看看手指抬起时是怎么处理长按事件的。
case MotionEvent.ACTION_UP:
...
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.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
//1
performClickInternal();
}
}
}
...
其实,在 onLongClick 方法返回 true 后整个长按事件就处理完毕了。上述代码是来处理返回 false 的。
前面分析过onLongClick 方法返回false,mHasPerformedLongPress也会跟着变为false,表示这个长按事件并没有被消耗掉。于是运行代码1执行短按的回调,也就是触发了OnClickListener的回调。
优先级比较
上一篇文章我们知道OnTouchListener 可以在 OnClickListener 和 OnLongClickListener 之前把ACTION_DOWN 和 ACTION_UP事件消耗掉(也就是返回true),所以 OnTouchListener 优先级高于 OnClickListener 和 OnLongClickListener。
刚刚的对 mHasPerformedLongPress 标记的分析我们知道 OnLongClickListener 的回调返回false,OnClickListener 的回调才会被触发。所以 OnLongClickListener 优先级高于 OnClickListener。
优先级:OnTouchListener > OnLongClickListener > OnClickListener