5.4 OnLongClickListener事件处理流程
5.4.1 OnLongClickListener注册和使用
其注册和使用方法和OnClickListener相似,在activity里实现接口,
public class Launcher extends Activity
implements View.OnLongClickListener,
view设置监听器,
mWorkspace.setOnLongClickListener(this);
view基类里调用performLongClick,执行onLongClick()
public boolean performLongClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLongClickListener != null) {
handled = li.mOnLongClickListener.onLongClick(View.this);
}
if (!handled) {
handled = showContextMenu();
}
if (handled) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
return handled;
}
5.4.2 OnClickListener栈分析
栈的调用过程和OnClickListener也类似,用异步的方式调用到activity的onLongClick方法,
Launcher.onLongClick(View) line: 5169
CellLayout(View).performLongClick() line: 5244
View$CheckForLongPress.run() line: 21138
Handler.handleCallback(Message) line: 739
ViewRootImpl$ViewRootHandler(Handler).dispatchMessage(Message) line: 95
Looper.loop() line: 148
ActivityThread.main(String[]) line: 5524
Method.invoke(Object, Object...) line: not available [native method]
ZygoteInit$MethodAndArgsCaller.run() line: 752
ZygoteInit.main(String[]) line: 642
所以我们看一下消息是如何post出来的即可。
在Android的触摸消息中,已经实现了三种监测,它们分别是
1)pre-pressed:对应的语义是用户轻触(tap)了屏幕
2)pressed:对应的语义是用户点击(press)了屏幕
3)long pressed:对应的语义是用户长按(long press)了屏幕
下图是触摸消息随时间变化的时间轴示意图:
其中,t0和t1定义在ViewConfiguration类中,标识了tap和longpress的超时时间,定义如下:
private static final int TAP_TIMEOUT = 100;
private static final int DEFAULT_LONG_PRESS_TIMEOUT = 500;
在早期版本中,在View类的OnTouchEvent函数中,当View监测到ACTION_DOWN事件时,首先发送一个延迟为t0的异步消息处理tap,在Android6.0中则不使用这种机制了。
长按的调用栈:
onTouchEvent --checkForLongClick –post
—CheckForLongPress--run --performLongClick-- .mOnLongClickListener.onLongClick
如前分析,在onTouchEvent的MotionEvent.ACTION_DOWN,执行checkForLongClick,延时post一个长按消息出去。
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0);
}
break;
在MotionEvent.ACTION_UP及MOVE\CANCEL中, 会清除长按的异步消息,设置相关的变量和按键状态,这样,如果这些事件产生在长按超期前,长按就不起作用;在长按超期后,长按有效,并完成了按键的周期流程。所以按键处理的逻辑就是这样的。
removeLongPressCallback();
setPressed(false);
onClick和onLongClick能同时发生吗?
要理解Android对事件处理的所谓消费(consume)概念即可,一个用户的操作会被传递到不同的View控件和同一个控件的不同监听方法处理,任何一个接收并处理了该次事件的方法如果在处理完后返回了true,那么该次event就算被完全处理了,其他的View或者监听方法就不会再有机会处理该event了。
onLongClick的发生是由单独的线程完成的,一般发生在ACTION_UP之前,而onClick的发生是在ACTION_UP后。临界条件是同时发生,这里会有flag来进行区分。
因此同一次用户touch操作就有可能既发生onLongClick又发生onClick。
及时向系统表示“我已经完全处理(消费)了用户的此次操作”,是很重要的事情。
另外一个同时执行的概念(都执行的意思),先后执行,例如,我们如果在onLongClick()方法的最后return true,那么onClick事件就没有机会被触发了。
在onLongClick()方法return false的情况下,一次触碰操作的基本时序:
04-05 06:00:53.023: DEBUG/TSActivity(277): onTouch ACTION_DOWN
04-05 06:00:53.533: DEBUG/TSActivity(277): onLongClick
04-05 06:00:55.603: DEBUG/TSActivity(277): onTouch ACTION_UP
04-05 06:00:55.663: DEBUG/TSActivity(277): onClick
可以看到,在ACTION_UP后仍然触发了onClick()方法。