Android系统中长按事件的实现机制解析

 

在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  
    /**
     * Defines the duration in milliseconds we will wait to see if a touch event 
     * is a tap or a scroll. If the user does not move within this interval, it is
     * considered to be a tap. 
     */
    private static final int TAP_TIMEOUT = 115; // t0
    
    /**
     * Defines the duration in milliseconds before a press turns into
     * a long press
     */
    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;  
    case MotionEvent.ACTION_DOWN:
        if (mPendingCheckForTap == null) {
            mPendingCheckForTap = new CheckForTap();
        }
        mPrivateFlags |= PREPRESSED;
        mHasPerformedLongPress = false;
        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
        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;  
    case MotionEvent.ACTION_UP:
        boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; //获取prepressed状态
        if ((mPrivateFlags & PRESSED) != 0 || prepressed) { //如果是pressed状态或者是prepressed状态,才进行处理
        	// 如果当前view不具有焦点,则需要先获取焦点,因为我们当前处理触摸模式
            boolean focusTaken = false;
            if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                focusTaken = requestFocus(); // 请求获得焦点
            }

            if (!mHasPerformedLongPress) { // 是否处理过长按操作了,如果是,则直接返回
            	// 进入该代码段,说明这是一个tap操作,首先移除长按回调操作
                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)) {
                        performClick(); // 执行点击的处理函数
                    }
                }
            }

            if (mUnsetPressedState == null) {
                mUnsetPressedState = new UnsetPressedState();
            }

            if (prepressed) {
                mPrivateFlags |= PRESSED;
                refreshDrawableState();
                // 发送重置触摸状态的异步延迟消息
                postDelayed(mUnsetPressedState,
                        ViewConfiguration.getPressedStateDuration());
            } else if (!post(mUnsetPressedState)) {
                // If the post failed, unpress right now
                mUnsetPressedState.run();
            }
            removeTapCallback(); // 移除tap的回调操作
        }
        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. }  
	    public boolean postDelayed(Runnable action, long delayMillis) {
	        Handler handler;
	        if (mAttachInfo != null) {
	            handler = mAttachInfo.mHandler;
	        } else {
	            // Assume that post will succeed later
	            ViewRoot.getRunQueue().postDelayed(action, delayMillis);
	            return true;
	        }

	        return handler.postDelayed(action, delayMillis);
	    }

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. }  
	    private final class CheckForTap implements Runnable {
	        public void run() {
	        	// 进入该函数,说明已经过了ViewConfiguration.getTapTimeout()时间,
	        	// 即pre-pressed状态结束,宣告触摸进入pressed状态
	            mPrivateFlags &= ~PREPRESSED; 
	            mPrivateFlags |= PRESSED;
	            refreshDrawableState(); // 刷新控件的背景Drawable
	            // 如果长按检测没有被去使能,则发送一个检测长按事件的异步延迟消息
	            if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
	                postCheckForLongClick(ViewConfiguration.getTapTimeout());
	            }
	        }
	    }
	    
	    private void postCheckForLongClick(int delayOffset) {
	        mHasPerformedLongPress = false;

	        // 实例化CheckForLongPress对象
	        if (mPendingCheckForLongPress == null) {
	            mPendingCheckForLongPress = new CheckForLongPress();
	        }
	        mPendingCheckForLongPress.rememberWindowAttachCount();
	        // 调用PostDelayed函数发送长按事件的异步延迟消息
	        postDelayed(mPendingCheckForLongPress,
	                ViewConfiguration.getLongPressTimeout() - delayOffset);
	    }

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

转载于:https://www.cnblogs.com/nafio/archive/2012/08/13/9137773.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值