在Android的触摸消息中,已经实现了三种监测,它们分别是
1)pre-pressed:对应的语义是用户轻触(tap)了屏幕
2)pressed:对应的语义是用户点击(press)了屏幕
3)long pressed:对应的语义是用户长按(long press)了屏幕
下图是触摸消息随时间变化的时间轴示意图:
其中,t0和t1定义在ViewConfiguration类中,标识了tap和longpress的超时时间,定义如下:
1./**
2. * Defines the duration in milliseconds we will wait to see if a touch event
3. * is a tap or a scroll. If the user does not move within this interval, it is
4. * considered to be a tap.
5. */
6.private static final int TAP_TIMEOUT = 115; // t0
7.
8./**
9. * Defines the duration in milliseconds before a press turns into
10. * a long press
11. */
12.private static final int LONG_PRESS_TIMEOUT = 500; // t1
代码中实现监测的地方在View类的OnTouchEvent函数中,当View监测到ACTION_DOWN事件时,首先发送一个延迟为t0的异步消息,代码如下:
1.case MotionEvent.ACTION_DOWN:
2. if (mPendingCheckForTap == null) {
3. mPendingCheckForTap = new CheckForTap();
4. }
5. mPrivateFlags |= PREPRESSED;
6. mHasPerformedLongPress = false;
7. postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
8. break;
如果在t0时间内用户释放了屏幕,即ACTION_UP事件在t0时间内发生,则本次触摸对应的是pre-pressed处理代码,语义是"用户轻触(TAP)了一下屏幕";如果用户在t1时间内释放了屏幕,那么本次操作是一个"press"操作;如果用户超过t1时间释放屏幕,则系统认为监测到了长按事件。其中处理"press"操作的代码在类CheckForTap类中,处理长按操作的代码在类CheckForLongPress类中。而处理pre-pressed的代码在ACTION_UP事件响应中,ACTION_UP事件响应中大部分代码用于处理触摸的状态变化,如下所示:
1.case MotionEvent.ACTION_UP:
2. boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; //获取prepressed状态
3. if ((mPrivateFlags & PRESSED) != 0 || prepressed) { //如果是pressed状态或者是prepressed状态,才进行处理
4. // 如果当前view不具有焦点,则需要先获取焦点,因为我们当前处理触摸模式
5. boolean focusTaken = false;
6. if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
7. focusTaken = requestFocus(); // 请求获得焦点
8. }
9.
10. if (!mHasPerformedLongPress) { // 是否处理过长按操作了,如果是,则直接返回
11. // 进入该代码段,说明这是一个tap操作,首先移除长按回调操作
12. removeLongPressCallback();
13.
14. // Only perform take click actions if we were in the pressed state
15. if (!focusTaken) {
16. // Use a Runnable and post this rather than calling
17. // performClick directly. This lets other visual state
18. // of the view update before click actions start.
19. if (mPerformClick == null) {
20. mPerformClick = new PerformClick();
21. }
22. if (!post(mPerformClick)) {
23. performClick(); // 执行点击的处理函数
24. }
25. }
26. }
27.
28. if (mUnsetPressedState == null) {
29. mUnsetPressedState = new UnsetPressedState();
30. }
31.
32. if (prepressed) {
33. mPrivateFlags |= PRESSED;
34. refreshDrawableState();
35. // 发送重置触摸状态的异步延迟消息
36. postDelayed(mUnsetPressedState,
37. ViewConfiguration.getPressedStateDuration());
38. } else if (!post(mUnsetPressedState)) {
39. // If the post failed, unpress right now
40. mUnsetPressedState.run();
41. }
42. removeTapCallback(); // 移除tap的回调操作
43. }
44. break;
在上面的代码分析中,可以看出,整个监测过程涉及到两个Runnable对象和一个利用Handler发送异步延迟消息的函数,下面就来分析一下:
1)PostDelayed函数
该函数的主要工作是获取UI线程的Handler对象,然后调用Handler类的postDelayed函数将指定的Runnable对象放到消息队列中。
1.public boolean postDelayed(Runnable action, long delayMillis) {
2. Handler handler;
3. if (mAttachInfo != null) {
4. handler = mAttachInfo.mHandler;
5. } else {
6. // Assume that post will succeed later
7. ViewRoot.getRunQueue().postDelayed(action, delayMillis);
8. return true;
9. }
10.
11. return handler.postDelayed(action, delayMillis);
12.}
2)CheckForTap类
该类实现了Runnable接口,在run函数中设置触摸标识,并刷新Drawable的状态,同时用于发送一个检测长按事件的异步延迟消息,代码如下:
1.private final class CheckForTap implements Runnable {
2. public void run() {
3. // 进入该函数,说明已经过了ViewConfiguration.getTapTimeout()时间,
4. // 即pre-pressed状态结束,宣告触摸进入pressed状态
5. mPrivateFlags &= ~PREPRESSED;
6. mPrivateFlags |= PRESSED;
7. refreshDrawableState(); // 刷新控件的背景Drawable
8. // 如果长按检测没有被去使能,则发送一个检测长按事件的异步延迟消息
9. if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
10. postCheckForLongClick(ViewConfiguration.getTapTimeout());
11. }
12. }
13.}
14.
15.private void postCheckForLongClick(int delayOffset) {
16. mHasPerformedLongPress = false;
17.
18. // 实例化CheckForLongPress对象
19. if (mPendingCheckForLongPress == null) {
20. mPendingCheckForLongPress = new CheckForLongPress();
21. }
22. mPendingCheckForLongPress.rememberWindowAttachCount();
23. // 调用PostDelayed函数发送长按事件的异步延迟消息
24. postDelayed(mPendingCheckForLongPress,
25. ViewConfiguration.getLongPressTimeout() - delayOffset);
26.}
3)CheckForLongPress类
该类定义了长按操作发生时的响应处理,同样实现了Runnable接口
1.class CheckForLongPress implements Runnable {
2.
3. private int mOriginalWindowAttachCount;
4.
5. public void run() {
6. // 进入该函数,说明检测到了长按操作
7. if (isPressed() && (mParent != null)
8. && mOriginalWindowAttachCount == mWindowAttachCount) {
9. if (performLongClick()) {
10. mHasPerformedLongPress = true;
11. }
12. }
13. }
14.
15. public void rememberWindowAttachCount() {
16. mOriginalWindowAttachCount = mWindowAttachCount;
17. }
18.}
19.
20.public boolean performLongClick() {
21. sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
22.
23. boolean handled = false;
24. if (mOnLongClickListener != null) {
25. // 回调用户实现的长按操作监听函数(OnLongClickListener)
26. handled = mOnLongClickListener.onLongClick(View.this);
27. }
28. if (!handled) {
29. // 如果OnLongClickListener的onLongClick返回false
30. // 则需要继续处理该长按事件,这里是显示上下文菜单
31. handled = showContextMenu();
32. }
33. if (handled) {
34. // 长按操作事件被处理了,此时应该给用户触觉上的反馈
35. performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
36. }
37. return handled;
38.}