<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # Android 手勢檢測(GestureDetector) Android 手勢檢測,主要是 GestureDetector 相關內容的用法和注意事項,本文依舊屬于事件處理這一體系,部分內容會涉及到之前文章提及過的知識點,如果你沒看過之前的文章,可以到 [自定義 View 系列](http://www.gcssloop.com/customview/CustomViewIndex) 來查看這些內容。 在開發 Android 手機應用過程中,可能需要對一些手勢作出響應,如:單擊、雙擊、長按、滑動、縮放等。這些都是很常用的手勢。就拿最簡單的雙擊來說吧,假如我們需要判斷一個控件是否被雙擊(即在較短的時間內快速的點擊兩次),似乎是一個很容易的任務,但仔細考慮起來,要處理的細節問題也有不少,例如: 1. **記錄點擊次數**,為了判斷是否被點擊超過 1 次,所以必須記錄點擊次數。 2. **記錄點擊時間**,由于雙擊事件是較快速的點擊兩次,像點擊一次后,過來幾分鐘再點擊一次肯定不能算是雙擊事件,所以在記錄點擊次數的同時也要記錄上一次的點擊時間,我們可以設置本次點擊距離上一次時間超過一定時間(例如:超過100ms)就不識別為雙擊事件。 3. **點擊狀態重置**,在響應雙擊事件,或者判斷不是雙擊事件的時候要重置計數器和上一次點擊時間。重置既可以在點擊的時候判斷并進行重新設置,也可以使用定時器等超過一定時間后重置狀態。 這樣看起來,判斷一個雙擊事件就有這么多麻煩事情,更別其他的手勢了,雖然這些看起來都很簡單,但設計起來需要考慮的細節情況實在是太多了。 那么有沒有一種更好的方法來方便的檢測手勢呢?當然有啦,因為這些手勢很常用,系統早就封裝了一些方法給我們用,接下來我們就看看它們是如何使用的。 ## GestureDetector > GestureDetector 可以使用 MotionEvents 檢測各種手勢和事件。GestureDetector.OnGestureListener 是一個回調方法,在發生特定的事件時會調用 Listener 中對應的方法回調。這個類只能用于檢測觸摸事件的 MotionEvent,不能用于軌跡球事件。 > (話說軌跡球已經消失多長時間了,估計很多人都沒見過軌跡球這種東西)。 > > 如何使用: > > - 創建一個 GestureDetector 實例。 > - 在onTouchEvent(MotionEvent)方法中,確保調用 GestureDetector 實例的 onTouchEvent(MotionEvent)。回調中定義的方法將在事件發生時執行。 > - 如果偵聽 onContextClick(MotionEvent),則必須在 View 的 onGenericMotionEvent(MotionEvent)中調用 GestureDetector OnGenericMotionEvent(MotionEvent)。 GestureDetector 本身的方法比較少,使用起來也非常簡單,下面讓我們先看一下它的簡單使用示例,分解開來大概需要三個步驟。 ```java // 1.創建一個監聽回調 SimpleOnGestureListener listener = new SimpleOnGestureListener() { @Override public boolean onDoubleTap(MotionEvent e) { Toast.makeText(MainActivity.this, "雙擊666", Toast.LENGTH_SHORT).show(); return super.onDoubleTap(e); } }; // 2.創建一個檢測器 final GestureDetector detector = new GestureDetector(this, listener); // 3.給監聽器設置數據源 view.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return detector.onTouchEvent(event); } }); ``` 接下來我們先了解一下 GestureDetector 里面都有哪些內容。 ### 1. 構造函數 GestureDetector 一共有 5 種構造函數,但有 2 種被廢棄了,1 種是重復的,所以我們只需要關注其中的 2 種構造函數即可,如下: | 構造函數 | | ---------------------------------------- | | GestureDetector(Context context, GestureDetector.OnGestureListener listener) | | GestureDetector(Context context, GestureDetector.OnGestureListener listener, Handler handler) | 第 1 種構造函數里面需要傳遞兩個參數,上下文(Context) 和 手勢監聽器(OnGestureListener),這個很容易理解,就不再過多敘述,上面的例子中使用的就是這一種。 第 2 種構造函數則需要多傳遞一個 Handler 作為參數,這個有什么作用呢?其實作用也非常簡單,這個 Handler 主要是為了給 GestureDetector 提供一個 Looper。 在通常情況下是不需這個 Handler 的,因為它會在內部自動創建一個 Handler 用于處理數據,如果你在主線程中創建 GestureDetector,那么它內部創建的 Handler 會自動獲得主線程的 Looper,然而如果你在一個沒有創建 Looper 的子線程中創建 GestureDetector 則需要傳遞一個帶有 Looper 的 Handler 給它,否則就會因為無法獲取到 Looper 導致創建失敗。 第 2 種構造函數使用方式如下(下面是兩種在子線程中創建 GestureDetector 的方法): ```java // 方式一、在主線程創建 Handler final Handler handler = new Handler(); new Thread(new Runnable() { @Override public void run() { final GestureDetector detector = new GestureDetector(MainActivity.this, new GestureDetector.SimpleOnGestureListener() , handler); // ... 省略其它代碼 ... } }).start(); // 方式二、在子線程創建 Handler,并且指定 Looper new Thread(new Runnable() { @Override public void run() { final Handler handler = new Handler(Looper.getMainLooper()); final GestureDetector detector = new GestureDetector(MainActivity.this, new GestureDetector.SimpleOnGestureListener() , handler); // ... 省略其它代碼 ... } }).start(); ``` 當然了,使用其它創建 Handler 的方式也是可以的,重點傳遞的 Handler 一定要有 Looper,敲黑板,重點是 Handler 中的 Looper。假如子線程準備了 Looper 那么可以直接使用第 1 種構造函數進行創建,如下: ```java new Thread(new Runnable() { @Override public void run() { Looper.prepare(); // <- 重點在這里 final GestureDetector detector = new GestureDetector(MainActivity.this, new GestureDetector.SimpleOnGestureListener()); // ... 省略其它代碼 ... } }).start(); ``` ### 2.手勢監聽器 既然是手勢檢測,自然要在對應的手勢出現的時候通知調用者,最合適的自然是事件監聽器模式。目前 GestureDetecotr 有四種監聽器。 | 監聽器 | 簡介 | | ---------------------------------------- | ---------------------------------------- | | [OnContextClickListener](https://developer.android.com/reference/android/view/GestureDetector.OnContextClickListener.html) | 這個很容易讓人聯想到ContextMenu,然而它和ContextMenu并沒有什么關系,它是在Android6.0(API 23)才添加的一個選項,是用于檢測外部設備上的按鈕是否按下的,例如藍牙觸控筆上的按鈕,一般情況下,忽略即可。 | | [OnDoubleTapListener](https://developer.android.com/reference/android/view/GestureDetector.OnDoubleTapListener.html) | 雙擊事件,有三個回調類型:雙擊(DoubleTap)、單擊確認(SingleTapConfirmed) 和 雙擊事件回調(DoubleTapEvent) | | [OnGestureListener](https://developer.android.com/reference/android/view/GestureDetector.OnGestureListener.html) | 手勢檢測,主要有以下類型事件:按下(Down)、 一扔(Fling)、長按(LongPress)、滾動(Scroll)、觸摸反饋(ShowPress) 和 單擊抬起(SingleTapUp) | | [SimpleOnGestureListener](https://developer.android.com/reference/android/view/GestureDetector.SimpleOnGestureListener.html) | 這個是上述三個接口的空實現,一般情況下使用這個比較多,也比較方便。 | #### 2.1 OnContextClickListener 由于 OnContextClickListener 主要是用于檢測外部設備按鈕的,關于它需要注意一點,如果偵聽 onContextClick(MotionEvent),則必須在 View 的 onGenericMotionEvent(MotionEvent)中調用 GestureDetector 的 OnGenericMotionEvent(MotionEvent)。 由于目前我們用到這個監聽器的場景并不多,所以也就不展開介紹了,重點關注后面幾個監聽器。 #### 2.2 OnDoubleTapListener 這個很明顯就是用于檢測雙擊事件的,它有三個回調接口,分別是 onDoubleTap、onDoubleTapEvent 和 onSingleTapConfirmed。 ##### **2.2.1 onDoubleTap 與 onSingleTapConfirmed** **如果你只想監聽雙擊事件,那么只用關注 onDoubleTap 就行了,如果你同時要監聽單擊事件則需要關注 onSingleTapConfirmed 這個回調函數**。 有人可能會有疑問,監聽單擊事件為什么要使用 onSingleTapConfirmed,使用 OnClickListener 不行嗎?從理論上是可行的,但是我并不推薦這樣使用,主要有兩個原因: 1.它們兩個是存在一定沖突的,如果你看過 [事件分發機制詳解](http://www.gcssloop.com/customview/dispatch-touchevent-source) 就會知道,如果想要兩者同時被觸發,則 setOnTouchListener 不能消費事件,如果 onTouchListener 消費了事件,就可能導致 OnClick 無法正常觸發。 2.需要同時監聽單擊和雙擊,則說明單擊和雙擊后響應邏輯不同,然而使用 OnClickListener 會在雙擊事件發生時觸發兩次,這顯然不是我們想要的結果。而使用 onSingleTapConfirmed 就不用考慮那么多了,你完全可以把它當成單擊事件來看待,而且在雙擊事件發生時,onSingleTapConfirmed 不會被調用,這樣就不會引發沖突。 如果你需要同時監聽兩種點擊事件可以這樣寫: ```java GestureDetector detector = new GestureDetector(this, new GestureDetector .SimpleOnGestureListener() { @Override public boolean onSingleTapConfirmed(MotionEvent e) { Toast.makeText(MainActivity.this, "單擊", Toast.LENGTH_SHORT).show(); return false; } @Override public boolean onDoubleTap(MotionEvent e) { Toast.makeText(MainActivity.this, "雙擊", Toast.LENGTH_SHORT).show(); return false; } }); ``` 關于 onSingleTapConfirmed 原理也非常簡單,這一個回調函數在單擊事件發生后300ms后觸發(注意,不是立即觸發的),只有在確定不會有后續的事件后,既當前事件肯定是單擊事件才觸發 onSingleTapConfirmed,所以在進行點擊操作時,onDoubleTap 和 onSingleTapConfirmed 只會有一個被觸發,也就不存在沖突了。 當然,如果你對事件分發機制非常了解的話,隨便怎么用都行,條條大路通羅馬,我這里只是推薦一種最簡單而且不容易出錯的實現方案。 ##### **2.2.2 onDoubleTapEvent** **有些細心的小伙伴可能注意到還有一個 onDoubleTapEvent 回調函數,它是干什么的呢?它在雙擊事件確定發生時會對第二次按下產生的 MotionEvent 信息進行回調。** 至于為什么要存在這樣的回調,就要涉及到另一個比較細致的問題了,那就是 onDoubleTap 的觸發時間,如果你在這些函數被調用時打印一條日志,那么你會看到這樣的信息: ``` GCS-LOG: onDoubleTap GCS-LOG: onDoubleTapEvent - down GCS-LOG: onDoubleTapEvent - move GCS-LOG: onDoubleTapEvent - move GCS-LOG: onDoubleTapEvent - up ``` 通過觀察這些信息你會發現它們的調用順序非常有趣,首先是 onDoubleTap 被觸發,之后依次觸發 onDoubleTapEvent 的 down、move、up 等信息,為什么說它們有趣呢?是因為這樣的調用順序會引發兩種猜想,第一種猜想是 onDoubleTap 是在第二次手指抬起(up)后觸發的,而 onDoubleTapEvent 是一種延時回調。第二種猜想則是 onDoubleTap 在第二次手指按下(dowm)時觸發,onDoubleTapEvent 是一種實時回調。 通過測試和觀察源碼發現第二種猜想是正確的,因為第二次按下手指時,即便不抬起也會觸發 onDoubleTap 和 onDoubleTapEvent 的 down,而且源碼中邏輯也表明 onDoubleTapEvent 是一種實時回調。 這就引發了另一個問題,雙擊的觸發時間,雖然這是一個細微到很難讓人注意到的問題,假如說我們想要在第二次按下抬起后才判定這是一個雙擊操作,觸發后續的內容,則不能使用 onDoubleTap 了,需要使用 onDoubleTapEvent 來進行更細微的控制,如下: ```java final GestureDetector detector = new GestureDetector(MainActivity.this, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDoubleTap(MotionEvent e) { Logger.e("第二次按下時觸發"); return super.onDoubleTap(e); } @Override public boolean onDoubleTapEvent(MotionEvent e) { switch (e.getActionMasked()) { case MotionEvent.ACTION_UP: Logger.e("第二次抬起時觸發"); break; } return super.onDoubleTapEvent(e); } }); ``` 如果你不需要控制這么細微的話,忽略即可(Logger 是我自己封裝的日志庫,忽略即可)。 #### 2.3 OnGestureListener 這個是手勢檢測中較為核心的一個部分了,主要檢測以下類型事件:按下(Down)、 一扔(Fling)、長按(LongPress)、滾動(Scroll)、觸摸反饋(ShowPress) 和 單擊抬起(SingleTapUp)。 ##### 2.3.1 onDown ```java @Override public boolean onDown(MotionEvent e) { return true; } ``` 看過前面的文章應該知道,down 在事件分發體系中是一個較為特殊的事件,為了保證事件被唯一的 View 消費,哪個 View 消費了 down 事件,后續的內容就會傳遞給該 View。如果我們想讓一個 View 能夠接收到事件,有兩種做法: 1、讓該 View 可以點擊,因為可點擊狀態會默認消費 down 事件。 2、手動消費掉 down 事件。 由于圖片、文本等一些控件默認是不可點擊的,所以我們要么聲明它們的 clickable 為 true,要么在發生 down 事件是返回 true。所以 onDown 在這里的作用就很明顯了,就是為了保證讓該控件能擁有消費事件的能力,以接受后續的事件。 ##### 2.3.2 onFling Failing 中文直接翻譯過來就是一扔、拋、甩,最常見的場景就是在 ListView 或者 RecyclerView 上快速滑動時手指抬起后它還會滾動一段時間才會停止。onFling 就是檢測這種手勢的。 ```java @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return super.onFling(e1, e2, velocityX, velocityY); } ``` 在 onFling 的回調中共有四個參數,分別是: | 參數 | 簡介 | | --------- | ------------------ | | e1 | 手指按下時的 Event。 | | e2 | 手指抬起時的 Event。 | | velocityX | 在 X 軸上的運動速度(像素/秒)。 | | velocityY | 在 Y 軸上的運動速度(像素/秒)。 | 我們可以通過 e1 和 e2 獲取到手指按下和抬起時的坐標、時間等相關信息,通過 velocityX 和 velocityY 獲取到在這段時間內的運動速度,單位是像素/秒(即 1 秒內滑動的像素距離)。 這個我們自己用到的地方比較少,但是也可以幫助我們簡單的做出一些有趣的效果,例如下面的這種彈球效果,會根據滑動的力度和方向產生不同的彈跳效果。 ![](https://ww2.sinaimg.cn/large/006tKfTcgy1fhe10uwa4fg308c0e6wn5.gif) 其實這種原理非常簡單,簡化之后如下: 1. 記錄 velocityX 和 velocityY 作為初始速度,之后不斷讓速度衰減,直至為零。 2. 根據速度和當前小球的位置計算一段時間后的位置,并在該位置重新繪制小球。 3. 判斷小球邊緣是否碰觸控件邊界,如果碰觸了邊界則讓速度反向。 根據這三條基本的邏輯就可以做出比較像的彈球效果,[具體的Demo可以看這里](https://raw.githubusercontent.com/GcsSloop/AndroidNote/master/CustomView/Demo/FailingBall.zip)。 ##### 2.3.3 onLongPress 這個是檢測長按事件的,即手指按下后不抬起,在一段時間后會觸發該事件。 ```java @Override public void onLongPress(MotionEvent e) { } ``` ##### 2.3.4 onScroll onScroll 就是監聽滾動事件的,它看起來和 onFaling 比較像,不同的是,onSrcoll 后兩個參數不是速度,而是滾動的距離。 ```java @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return super.onScroll(e1, e2, distanceX, distanceY); } ``` | 參數 | | | --------- | ----------- | | e1 | 手指按下時的Event | | e2 | 手指抬起時的Event | | distanceX | 在 X 軸上劃過的距離 | | distanceY | 在 Y 軸上劃過的距離 | ##### 2.3.5 onShowPress 它是用戶按下時的一種回調,主要作用是給用戶提供一種視覺反饋,可以在監聽到這種事件時可以讓控件換一種顏色,或者產生一些變化,告訴用戶他的動作已經被識別。 不過這個消息和 onSingleTapConfirmed 類似,也是一種延時回調,延遲時間是 180 ms,假如用戶手指按下后立即抬起或者事件立即被攔截,時間沒有超過 180 ms的話,這條消息會被 remove 掉,也就不會觸發這個回調。 ```java @Override public void onShowPress(MotionEvent e) { } ``` ##### 2.3.6 onSingleTapUp ```java @Override public boolean onSingleTapUp(MotionEvent e) { return super.onSingleTapUp(e); } ``` 這個也很容易理解,就是用戶單擊抬起時的回調,但是它和上面的 `onSingleTapConfirmed` 之間有何不同呢?和 `onClick` 又有何不同呢? 單擊事件觸發: ```java GCS: onSingleTapUp GCS: onClick GCS: onSingleTapConfirmed ``` | 類型 | 觸發次數 | 摘要 | | -------------------- | ---- | ---- | | onSingleTapUp | 1 | 單擊抬起 | | onSingleTapConfirmed | 1 | 單擊確認 | | onClick | 1 | 單擊事件 | 雙擊事件觸發: ```java GCS: onSingleTapUp GCS: onClick GCS: onDoubleTap // <- 雙擊 GCS: onClick ``` | 類型 | 觸發次數 | 摘要 | | -------------------- | ---- | ------------ | | onSingleTapUp | 1 | 在雙擊的第一次抬起時觸發 | | onSingleTapConfirmed | 0 | 雙擊發生時不會觸發。 | | onClick | 2 | 在雙擊事件時觸發兩次。 | 可以看出來這三個事件還是有所不同的,根據自己實際需要進行使用即可 #### 2.4 SimpleOnGestureListener 這個里面并沒有什么內容,只是對上面三種 Listener 的空實現,在上面的例子中使用的基本都是這監聽器。因為它用起來更方便一點。 這主要是 GestureDetector 構造函數的設計問題,以只監聽 OnDoubleTapListener 為例,如果想要使用 OnDoubleTapListener 接口則需要這樣進行設置: ```java GestureDetector detector = new GestureDetector(this, new GestureDetector .SimpleOnGestureListener()); detector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() { @Override public boolean onSingleTapConfirmed(MotionEvent e) { Toast.makeText(MainActivity.this, "單擊確認", Toast.LENGTH_SHORT).show(); return false; } @Override public boolean onDoubleTap(MotionEvent e) { Toast.makeText(MainActivity.this, "雙擊", Toast.LENGTH_SHORT).show(); return false; } @Override public boolean onDoubleTapEvent(MotionEvent e) { // Toast.makeText(MainActivity.this,"",Toast.LENGTH_SHORT).show(); return false; } }); ``` 既然都已經創建 SimpleOnGestureListener 了,再創建一個 OnDoubleTapListener 顯然十分浪費,如果構造函數不使用 SimpleOnGestureListener,而是使用 OnGestureListener 的話,會多出幾個無用的空實現,顯然很浪費,所以在一般情況下,老老實實的使用 SimpleOnGestureListener 就好了。 ### 3. 相關方法 除了各類監聽器之外,與 GestureDetector 相關的方法其實并不多,只有幾個,下面來簡單介紹一下。 | 方法 | 摘要 | | ----------------------- | ---------------------------------------- | | setIsLongpressEnabled | 通過布爾值設置是否允許觸發長按事件,true 表示允許,false 表示不允許。 | | isLongpressEnabled | 判斷當前是否允許觸發長按事件,true 表示允許,false 表示不允許。 | | onTouchEvent | 這個是其中一個重要的方法,在最開始已經演示過使用方式了。 | | onGenericMotionEvent | 這個是在 API 23 之后才添加的內容,主要是為 OnContextClickListener 服務的,暫時不用關注。 | | setContextClickListener | 設置 ContextClickListener 。 | | setOnDoubleTapListener | 設置 OnDoubleTapListener 。 | ### 結語 關于手勢檢測部分的 GestureDetector 相關內容基本就這么多了,其實手勢檢測還有一個 ScaleGestureDetector 也是為手勢檢測服務的,限于篇幅,本次就講這么多吧。 其實手勢檢測輔助類 GestureDetector 本身并不是很復雜,帶上注釋等內容才不到1000行,感興趣的可以自己研究一下實現方式。 ## About Me ### 作者微博: <a href="http://weibo.com/GcsSloop" target="_blank">@GcsSloop</a> <a href="http://www.gcssloop.com/info/about" target="_blank"><img src="http://ww4.sinaimg.cn/large/005Xtdi2gw1f1qn89ihu3j315o0dwwjc.jpg" width="300" style="display:inline;" /></a> ## 參考資料 [文檔 · GestureDetector ](https://developer.android.com/reference/android/view/GestureDetector.html) [源碼 · GestureDetector](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/view/GestureDetector.java)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看