史上最會“抄”的程式設計師!這逆天技能,善於將系統原始碼為己用!

yilian發表於2020-03-30

起源於一位同事問我:“怎麼優雅的監聽雙擊”這個行為?

其實很多類似的事件相關的,我們都可以參考系統原始碼,因為有時候完全引入系統能力有些麻煩,我們可能就想順手實現一個功能。

例如上面同事問的:

怎麼優雅的監聽雙擊?

相信大家或多或少都有一些實現方案,不過系統有給我們提供 GestureDetector類,如果你熟知該類實現,那麼就能選擇出於系統一樣的方案,程式碼的認可度也會提高。

所以今天我們就藉機學習下:

GestureDetector關於支援的手勢是如何檢測的?

下面內容是小緣分析的。

我們在建立這個類例項的時候,需要把介面 OnGestureListener(用來監聽各種手勢)實現並作為引數傳進它的構造方法中:

解釋一下各個方法的回撥時機(都非常容易理解):

  • onDown:手指按下;

  • onShowPress:手指按下後,100毫秒內未抬起、未移動;

  • onSingleTapUp:手指按下後未移動,並在500毫秒內抬起(可以認定為單擊);

  • onScroll:手指拖動;

  • onLongPress:長按(手指按下後,500毫秒內未抬起、未移動);

  • onFling:手指快速拖動後鬆手(慣性滾動);

除了 OnGestureListener之外,還有一個 OnDoubleTapListener,看名字就能猜到是用來監聽雙擊事件的了:

解釋一下:

  • onSingleTapConfirmed:已經確認這是一次單擊事件,想觸發雙擊必須繼續快速點選兩次螢幕(即:手指抬起之後,300毫秒內沒等到手指再次按下);

  • onDoubleTap:觸發雙擊事件(手指抬起後300毫秒內再次按下(注意:是再次按下時就觸發,並不是等它抬起後才觸發))

  • onDoubleTapEvent:觸發雙擊後的手指觸控事件,包括 ACTION_DOWNACTION_MOVEACTION_UP(注意:在觸發長按後,不會繼續收到 ACTION_MOVE事件,因為在手指長按過程中,是不需要處理手指移動的動作的,也就是會直接忽略 ACTION_MOVE的事件。還有,此方法回撥後,在觸發長按事件之前,如有新手指按下,則不再認定是雙擊了,所以不會繼續回撥此方法,取而代之的是 onScroll)。此方法與上面的 onDoubleTap方法的區別就是, onDoubleTap在一次雙擊事件中只會回撥一次,而這個方法能回撥多次;

好,對它有個初步瞭解之後,來看看它是怎麼檢測這些事件的。

1,onDown

這個類的程式碼很少,不到800行(SDK28)。

首先來看onDown方法是在什麼時候回撥的 (可在剛剛的介面方法中  CTRL + Click對應的方法名來定位到具體呼叫的位置) :

超級簡單,監聽到 ACTION_DOWN事件就立即回撥了。

2,onShowPress

接著來看看 onShowPress方法(用剛剛說的方法來定位):

它會在 GestureHandler收到 whatSHOW_PRESS的訊息後回撥,看看在哪裡發的這個訊息:

emmm,同樣在收到 ACTION_DOWN後,會向 mHandler(也就是 GestureHandler)傳送一個指定時間的訊息,而這個時間就是事件按下的時間加上 TAP_TIMEOUT的時長,可以看到 TAP_TIMEOUT的值是根據 ViewConfigurationgetTapTimeout方法來獲取的,點開一看:

是100(ms),也就是說,當手指按下後,如果這個延時任務100毫秒內沒有被取消,那麼 onShowPress方法就會回撥。

3,onSingleTapUp & onScroll

好,現在來看看 onSingleTapUp

可以看到,它是在 ACTION_UP的時候回撥的,回撥需滿足三個條件,分別是:

1.mIsDoubleTapping為false(即雙擊事件未觸發);

2.mInLongPress為false(即長按事件未觸發);

3.mAlwaysInTapRegion為true;

mAlwaysInTapRegion什麼時候為 true,什麼時候為 false呢:

一共有三處賦值的地方,分別是:

1. ACTION_POINTER_DOWN(另一隻手指按下)時為 false,也就是說,如果第一隻手指按下後,100毫秒內有新的手指按下,那麼當手指抬起時不會觸發 onSingleTapUp

2. ACTION_DOWN(第一隻手指按下)時為 true

3. ACTION_MOVE時,看 else if裡面的那個 if, 它是判斷 distance(手指的移動距離)是否大於 slopSquare(觸發移動的最小距離),如果是的話,會回撥 onScroll方法,並把 mAlwaysInTapRegion設為 false,這就說明,如果手指按下100秒內開始了拖動的話,那麼 onSingleTapUp方法也是不會回撥的;

還可以看到當 mAlwaysInTapRegion被設為 false之後,下一次的 ACTION_MOVE到來時,如果沒有觸發雙擊(即上面的 mIsDoubleTapping為false)並且手指的水平或垂直移動距離不為0的話,就會一直回撥 onScroll方法。

好,現在 onScroll也講了,輪到 onLongPress了。

4,onLongPress

onShowPress方法一樣也是藉助 Handler的定時訊息機制來實現的,它在收到 LONG_PRESS的訊息之後,會呼叫 dispatchLongPress方法, dispatchLongPress方法首先會標記 mInLongPresstrue

(注意:這將會影響到上面說到的 onSingleTapUp的回撥,因為 onSingleTapUp的回撥條件是需要 mInLongPressfalse的(即未觸發長按事件))

接著就回撥 onLongPress方法。

那麼 LONG_PRESS訊息在什麼時候傳送的呢?

也是在 ACTION_DOWN的時候 :

在傳送訊息之前,會先檢查是否開啟了監聽長按事件,還有取消上一次發出且未執行的長按回撥任務。

可以看到定的時間為事件按下時間加上 getLongPressTimeout方法返回的時長,預設是500(ms),也就是當手指按下半秒後, onLongPress方法就會被回撥,當然了,前提是這個任務沒有被取消。

有以下幾種情況會導致長按回撥任務被取消:

500ms內有新手指按下;

500ms內觸發了 onScroll,即手指移動超過指定距離;

500ms內手指抬起;

500ms內收到了 ACTION_CANCEL事件(該 ACTION一般源自父容器的私自建立);

來看看 onFling

5,onFling

跟我們平時處理慣性滾動沒什麼區別,只是它在回撥之前會先判斷滑動的速度 是否大於 指定的最小速度,否則不進行滾行滾動。

好,最後我們來看一下 onSingleTapConfirmedonDoubleTaponDoubleTapEvent分別是怎麼處理的:

onSingleTapConfirmed、onDoubleTap、onDoubleTapEvent
  private class GestureHandler extends Handler {
      ......
      @Override
      public void handleMessage(Message msg) {
          switch (msg.what) {
              ......
              case TAP:
                  if (mDoubleTapListener != null) {
                      if (!mStillDown) {
                          mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
                      } else {
                          mDeferConfirmSingleTap = true;
                      }
                  }
                  break;
              ......
          }
      }
  }
  public boolean onTouchEvent(MotionEvent ev) {
      ......
      boolean handled = false;
      switch (action & MotionEvent.ACTION_MASK) {
          ......
          case MotionEvent.ACTION_DOWN:
              ......
              if (isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
                  mIsDoubleTapping = true;
                  handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
                  handled |= mDoubleTapListener.onDoubleTapEvent(ev);
              } else {
                  mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
              }
              mStillDown = true;
              ......
              break;
          case MotionEvent.ACTION_MOVE:
              if (mIsDoubleTapping) {
                    handled |= mDoubleTapListener.onDoubleTapEvent(ev);
              }
              ......
              break;
          case MotionEvent.ACTION_UP:
              mStillDown = false;
              if (mIsDoubleTapping) {
                    handled |= mDoubleTapListener.onDoubleTapEvent(ev);
              }
              ......
              if (mIsDoubleTapping) {
                  ......
              } else if (mInLongPress) {
                  ......
              } else if (mAlwaysInTapRegion) {
                  handled = mListener.onSingleTapUp(ev);
                  if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
                          mDoubleTapListener.onSingleTapConfirmed(ev);
                  }
              }
            ......
            break;
        ......
    }
    ......
    return handled;
}

首先看 onSingleTapConfirmed方法,它在兩個地方有呼叫,分別是:

GestureHandler收到 TAP訊息時

處理 ACTION_UP事件時

GestureHandler收到 TAP訊息時它會先檢查手指是否已經抬起( !mStillDown),如果已經抬起了的話,就會立即呼叫,否則把 mDeferConfirmSingleTap標記為 true,表示 onSingleTapConfirmed方法應在 ACTION_UP時回撥,可以看到在處理 ACTION_UP時,如果手指沒有移動過並且沒觸發長按的話,就會判斷 mDeferConfirmSingleTap是否為 true,是的話,就會回撥 onSingleTapConfirmed方法。

接著看 ACTION_DOWN,它會呼叫 isConsideredDoubleTap方法來判斷此次事件是否被認定是雙擊,如果不是,就會向 GestureHandler發一條延時訊息(延時回撥 onSingleTapConfirmed方法)

如果是的話,就會把 mIsDoubleTapping標記為 true,然後依次回撥 onDoubleTaponDoubleTapEvent。可以看到在 ACTION_MOVEACTION_UP中也會根據 mIsDoubleTapping來判斷是否繼續回撥 onDoubleTapEvent方法。

好,那現在來看一下,它究竟是怎麼認定為雙擊的,看看 isConsideredDoubleTap方法:

先是判斷了 mAlwaysInBiggerTapRegion,如果它為 false的話,則代表被其他動作( ACTION_MOVEACTION_CANCEL)中斷了雙擊事件的檢測,所以直接返回 false(即不認定是雙擊)了。

接著會判斷第二次按下與第一次按下的時間間隔,如果大於300毫秒則認定是超時,如果小於40毫秒也會忽略(太快了)。

最後,可能很多同學咋一看,看不出來是什麼邏輯,仔細看幾次,就會知道這其實是在計算第一次按下和第二次按下的座標間隔距離,用的就是計算兩點間距離的公式(√(x1 - x2)² + (y1 - y2)²)。

這時有同學可能會問:

不是還要開平方嗎?怎麼它程式碼裡沒有呢?

其實,那個 slopSquare(也就是能夠被認定為雙擊的最大間隔)在初始化時,就已經作了平方運算了,所以這裡就不需要開平方了。

emmm,那麼 isConsideredDoubleTap方法最後一句的意思就是,判斷兩次觸控事件的座標間隔是否在指定的最大間隔範圍內,如果是的話,則認定是雙擊。

最後附上我的Android核心技術學習大綱,獲取相關內容來我的GitHub一起玩耍:

你把你的時間投資在學習上,就意味著你可以收穫技能,更有機會增加收入。

在這裡分享我的  來學習,這份Android學習PDF大全真的包含了方方面面了,內含Java基礎知識點、Android基礎、Android進階延伸、演算法合集等等


Android相關學習內容: 關注我看個人介紹,或者直接 私信

我的這份學習合集,可以有效的幫助大家掌握知識點。

總之也是在這裡幫助大家學習提升進階,也節省大家在網上搜尋資料的時間來學習,也可以分享給身邊好友一起學習


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952849/viewspace-2683388/,如需轉載,請註明出處,否則將追究法律責任。

相關文章