zoukankan      html  css  js  c++  java
  • 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  

    代码中实现监测的地方在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. 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. }  
  • 相关阅读:
    【乱侃】How do they look them ?
    【softeware】Messy code,some bug of Youdao notebook in EN win7
    【随谈】designing the login page of our project
    【web】Ad in security code, making good use of resource
    SQL数据库内存设置篇
    关系数据库的查询优化策略
    利用SQL未公开的存储过程实现分页
    sql语句总结
    sql中使用cmd命令注销登录用户
    SQLServer 分页存储过程
  • 原文地址:https://www.cnblogs.com/rainey-forrest/p/3728269.html
Copyright © 2011-2022 走看看