1. 程式人生 > >Android系統中長按事件的實現機制解析

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.  * Defines the duration in milliseconds before a press turns into 
  9.  * a long press 
  10.  */  
  11. 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.         if (!mHasPerformedLongPress) { // 是否處理過長按操作了,如果是,則直接返回   
  10.             // 進入該程式碼段,說明這是一個tap操作,首先移除長按回調操作   
  11.             removeLongPressCallback();   
  12.             // Only perform take click actions if we were in the pressed state   
  13.             if (!focusTaken) {  
  14.                 // Use a Runnable and post this rather than calling   
  15.                 // performClick directly. This lets other visual state   
  16.                 // of the view update before click actions start.   
  17.                 if (mPerformClick == null) {  
  18.                     mPerformClick = new PerformClick();  
  19.                 }  
  20.                 if (!post(mPerformClick)) {  
  21.                     performClick(); // 執行點選的處理函式   
  22.                 }  
  23.             }  
  24.         }  
  25.         if (mUnsetPressedState == null) {  
  26.             mUnsetPressedState = new UnsetPressedState();  
  27.         }  
  28.         if (prepressed) {  
  29.             mPrivateFlags |= PRESSED;  
  30.             refreshDrawableState();  
  31.             // 傳送重置觸控狀態的非同步延遲訊息   
  32.             postDelayed(mUnsetPressedState,  
  33.                     ViewConfiguration.getPressedStateDuration());  
  34.         } else if (!post(mUnsetPressedState)) {  
  35.             // If the post failed, unpress right now   
  36.             mUnsetPressedState.run();  
  37.         }  
  38.         removeTapCallback(); // 移除tap的回撥操作   
  39.     }  
  40.     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.     return handler.postDelayed(action, delayMillis);  
  11. }  
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. private void postCheckForLongClick(int delayOffset) {  
  15.     mHasPerformedLongPress = false;  
  16.     // 例項化CheckForLongPress物件   
  17.     if (mPendingCheckForLongPress == null) {  
  18.         mPendingCheckForLongPress = new CheckForLongPress();  
  19.     }  
  20.     mPendingCheckForLongPress.rememberWindowAttachCount();  
  21.     // 呼叫PostDelayed函式傳送長按事件的非同步延遲訊息   
  22.     postDelayed(mPendingCheckForLongPress,  
  23.             ViewConfiguration.getLongPressTimeout() - delayOffset);  
  24. }  

3)CheckForLongPress類

該類定義了長按操作發生時的響應處理,同樣實現了Runnable介面

  1. class CheckForLongPress implements Runnable {  
  2.     private int mOriginalWindowAttachCount;  
  3.     public void run() {  
  4.         // 進入該函式,說明檢測到了長按操作   
  5.         if (isPressed() && (mParent != null)  
  6.                 && mOriginalWindowAttachCount == mWindowAttachCount) {  
  7.             if (performLongClick()) {   
  8.                 mHasPerformedLongPress = true;  
  9.             }  
  10.         }  
  11.     }  
  12.     public void rememberWindowAttachCount() {  
  13.         mOriginalWindowAttachCount = mWindowAttachCount;  
  14.     }  
  15. }  
  16. public boolean performLongClick() {  
  17.     sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);  
  18.     boolean handled = false;  
  19.     if (mOnLongClickListener != null) {  
  20.         // 回撥使用者實現的長按操作監聽函式(OnLongClickListener)   
  21.         handled = mOnLongClickListener.onLongClick(View.this);  
  22.     }  
  23.     if (!handled) {  
  24.         // 如果OnLongClickListener的onLongClick返回false   
  25.         // 則需要繼續處理該長按事件,這裡是顯示上下文選單   
  26.         handled = showContextMenu();  
  27.     }  
  28.     if (handled) {  
  29.         // 長按操作事件被處理了,此時應該給使用者觸覺上的反饋   
  30.         performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);  
  31.     }  
  32.     return handled;  
  33. }  
linux