Android系統中長按事件的實現機制解析
阿新 • • 發佈:2019-02-01
在Android的觸控訊息中,已經實現了三種監測,它們分別是
1)pre-pressed:對應的語義是使用者輕觸(tap)了螢幕
2)pressed:對應的語義是使用者點選(press)了螢幕
3)long pressed:對應的語義是使用者長按(long press)了螢幕
下圖是觸控訊息隨時間變化的時間軸示意圖:
其中,t0和t1定義在ViewConfiguration類中,標識了tap和longpress的超時時間,定義如下:
- /**
- * 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
- case MotionEvent.ACTION_DOWN:
- if (mPendingCheckForTap == null) {
- mPendingCheckForTap = new CheckForTap();
- }
- mPrivateFlags |= PREPRESSED;
- mHasPerformedLongPress = false;
- postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
- 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;
1)PostDelayed函式
該函式的主要工作是獲取UI執行緒的Handler物件,然後呼叫Handler類的postDelayed函式將指定的Runnable物件放到訊息佇列中。
- 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);
- }
該類實現了Runnable介面,在run函式中設定觸控標識,並重新整理Drawable的狀態,同時用於傳送一個檢測長按事件的非同步延遲訊息,程式碼如下:
- 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介面
- class CheckForLongPress implements Runnable {
- private int mOriginalWindowAttachCount;
- public void run() {
- // 進入該函式,說明檢測到了長按操作
- if (isPressed() && (mParent != null)
- && mOriginalWindowAttachCount == mWindowAttachCount) {
- if (performLongClick()) {
- mHasPerformedLongPress = true;
- }
- }
- }
- public void rememberWindowAttachCount() {
- mOriginalWindowAttachCount = mWindowAttachCount;
- }
- }
- public boolean performLongClick() {
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
- boolean handled = false;
- if (mOnLongClickListener != null) {
- // 回撥使用者實現的長按操作監聽函式(OnLongClickListener)
- handled = mOnLongClickListener.onLongClick(View.this);
- }
- if (!handled) {
- // 如果OnLongClickListener的onLongClick返回false
- // 則需要繼續處理該長按事件,這裡是顯示上下文選單
- handled = showContextMenu();
- }
- if (handled) {
- // 長按操作事件被處理了,此時應該給使用者觸覺上的反饋
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- }
- return handled;
- }