Android OnLongClickListener原理解析及优先级分析

上一篇对 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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值