在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.}