Android觸摸事件(一)-TouchEventHelper

文件夾

概述

這是一個 觸摸 事件統一處理輔助類;處理的主要是點擊事件,當中包含了:java

  • 單點觸摸事件
  • 多點(兩點)觸摸事件

此類可以處理的事件包含:git

  • 單擊事件(基於時間與距離的兩種單擊事件,詳見下文)
  • 雙擊事件
  • 單點觸摸移動事件(可用於實現界面拖動)
  • 多點觸摸移動事件(可用於實現界面縮放)
  • 所有觸摸事件的相應回調(down/move/up事件)

此類實現View.onTouchListener,經過此監聽方法實現對觸摸事件的操做.在Activity中也可以使用,調用時onTouch(View,MotionEvent)傳遞的參數中將view設置爲null就能夠.
整個觸摸事件處理中並不會涉及不論什麼跟view有關的操做,僅分析及處理MotionEventgithub


關於更新

2016-08-31

修正部分邏輯,加入了雙擊是否可用的設置.改動了文章中一些相應的部分及說明.安全

2016-06-20

AbsTouchEventHandle抽象類改動爲TouchEventHelper的輔助類,將抽象方法抽出到接口OnToucheEventListener中,經過爲helper設置相應的事件處理接口就能夠直接處理事件.
改動爲helper的輔助類以後,就需要經過調用其方法來處理觸摸事件.TouchEventHelper實現了View.onTouchListnener接口,因此對事件的處理需要經過下面的方式調用.markdown

//參數爲事件處理接口 onTouchEventListener
TouchEventHelper helper=new TouchEventHelper(this);
//直接在需要處理觸摸事件的地方調用onTouch方法,如:
//onTouchEvent(MotionEvent event),在view中
//dispatchTouchEvent(MotionEvent event),在view,activity或者viewGroup中
helper.onTouch(null,event);
//或者直接使用view.setOnTouchListener(helper)
//activity中沒法使用setOnTouchListener(),因此僅僅能在相應的方法中進行調用

關於單點觸摸事件(singleTouch)

單點觸摸事件很是好理解,觸發的流程一般是: ide

mouse_down -> mouse_move -> mouse_uppost

這個過程可能發生的事件有下面幾種:優化

  • 單擊事件
  • 單點移動事件
  • 雙擊事件

但是必須注意的點是:this

單擊事件可能觸發mouse_move事件,而單點移動必然觸發mouse_move事件spa

在單擊事件中,mouse_move事件並不是百分百會觸發的,觸摸的時候先觸發的是mouse_down事件,假設觸摸的時間足夠長(按住不動時),接下來會觸發mouse_move事件,以後擡起時會觸發mouse_up事件
雖然觸發了mouse_move事件(按住不動),但是這依舊是一個單擊事件,假設進行調試或者輸出移動的距離,可以明顯獲得距離爲 0


單擊的兩種方式

  • 基於時間的單點觸摸事件(singleTouchByTime)

以時間來計算單擊事件時,這個過程可以沒必要過多地考慮單擊可能觸發的mouse_move事件,因爲單擊自己就是一個時間足夠短的操做,即使存在必定小範圍的移動誤差也是贊成的,固然這樣的狀況是在 時間足夠短 的狀況下
咱們可以這麼處理:

//定義全局變量用於存放按下時的時間點
long downTime=0;
switch(event.getAction()){
    case MotionEvent.ACTION_DOWN:
        //觸摸時記錄當前時間
        downTime=System.currentTimeMillis();
        break;
    case MotionEvent.ACTION_UP:
        //擡起時計算與按下時時間的差
        long tillTime=System.currentTimeMillis()-downTime;
        //時間差在贊成範圍內時,視爲一次單擊事件成立
        if(tillTime<150){
            //處理單擊事件
        }
        //不然不視爲一次單擊事件
        break;
}

經過計算按下時與擡起時的時間差來肯定是不是一次單擊事件(150ms足夠了),這是基於時間的單擊事件;


  • 基於距離的單點觸摸事件(singleTouchByDistance)

從上面咱們知道單點觸摸時也是可能觸發mouse_move事件的,因此

mouse_move事件並不能做爲一個是否單點移動的標識,實際上,多點觸摸的移動也會觸發mouse_move事件

而且咱們已經知道了單擊也可以是按住某個位置不動,持續一段時間以後再擡起,此時可能時間上已經達到一個足夠長的時間,但事實上點擊地方的座標並無改變,這樣的狀況下我將其也視爲單擊的一種狀況(總會在某些狀況下需要處理這樣的單擊方式)

參考 基於時間的單擊方式 的處理方法,咱們可以獲得類似的處理方法:

float downX=0;
float downY=0;
switch(event.getAction()){
    case MotionEvent.ACTION_DOWN:
        //觸摸時記錄當前觸摸點的座標
        downX=event.getX();
        downY=event.getY();
        break;
    case MotionEvent.ACTION_UP:
        //擡起時計算與按下時座標的偏移距離
        float offsetX=Math.abs(event.getX()-downX);
        float offsetY=Math.abs(event.getY()-downY);
        //偏移量差在贊成範圍內時,視爲一次單擊事件成立
        if(offsetX<20 && offsetY<20){
            //處理單擊事件
        }
        //不然不視爲一次單擊事件
        break;
}

以上爲兩種單擊方式的處理方式


關於雙擊事件

雙擊事件的檢測邏輯

因爲單擊事件存在兩種不一樣狀況,因此雙擊同理衍生出兩種方式, 基於時間和基於距離兩種雙擊事件
不管是哪一種方式,原理都是同樣的,基於相應的單擊方式實現第一次單擊,兩次單擊事件就構成了一次雙擊事件;
同一時候這裏存在一個問題是,雙擊不管從哪一個角度來講,都是指兩次時間間隔短暫的單擊事件,因此不管是基於時間仍是基於距離的雙擊事件,都是以兩次單擊時間以前的間隔時間不超過某個範圍來肯定一次雙擊事件的.

插個小話題 ----------
基於距離的雙擊事件事實上也是可以不按時間來處理的,僅僅要兩次單擊事件的距離在必定的偏移值範圍內,可以爲是一次雙擊事件(與時間無關);
但此方式存在的問題是,假設是兩次鏈接發生在同一個位置的單擊事件,此時就沒法正確的區分出到底是一次雙擊事件仍是兩次單擊事件了.因此並不推薦使用此方式處理,而是按兩次單擊事件間隔在必定時間差內視爲一次雙擊事件

由上可以看出,事實上這裏的雙擊事件構成該雙擊事件的單擊事件多是 基於時間的或者是基於距離的 單擊事件

//用於記錄是否已經完畢一次單擊事件
boolean isSingleClick=false;
switch(event.getAction()){
    //忽略ACTION_DOWN邏輯
    case MotionEvent.ACTION_UP:
        //達成一次單擊事件操做時,視爲一次單擊事件成立
        if(singleClickFinish){
            //推斷是否已經完畢了一次單擊事件(在贊成的雙擊間隔時間內)
            if(isSingleClick){
               //若已完畢了一次單擊事件,這次單擊構成了雙擊事件
                //處理雙擊事件
            }else{
                //僅爲一次單擊事件
                //處理單擊事件
                //記錄已經完畢了一次單擊事件
                isSingleClick=true;
            }
        }
        //不然不視爲一次單擊事件
        break;
}

同一時候,這裏有一個需要注意的地方是,雙擊事件本質是兩次單擊事件構成的,第一次單擊事件發生時咱們沒法肯定是不是一個正常的單擊事件仍是可能會構成一次雙擊事件,因此必須按正常單擊事件響應;

但第二次單擊事件發生時,咱們已經可以肯定構成了一次雙擊事件,此時不該該再響應單擊事件,而應該優先響應雙擊事件,且一旦響應了雙擊事件,就應該結束整個觸摸事件.

實際的處理事件並無這麼簡單,以上是簡單的處理邏輯,詳細的實現請參照下文 雙擊事件的優化處理


雙擊事件觸發的時機

雙擊事件觸發的時機是比較重要的.因爲雙擊事件是由單擊事件觸發的.必然先檢測單擊事件以後再檢測雙擊事件;
但一旦單擊事件被觸發了,那麼接下來需要作的操做有兩個選擇:

  • 檢測雙擊事件(以後運行雙擊事件)
  • 或運行單擊事件

這兩個事件的優先性是必須肯定的而且會形成不一樣的影響.
假設先運行單擊事件,則可能會形成在興許雙擊事件成立的以前,單擊事件會被運行一次.這並不合理,也可能存在一些不安全的因素(假設單擊操做會影響到雙擊操做的狀況下)

所以應先檢測雙擊事件,一旦雙擊事件成立,直接運行雙擊事件,同一時候忽略單擊事件;(用戶觸發了雙擊事件自己包含了不需要運行單擊事件的想法,不然直接觸發單擊事件就能夠)

這也是爲何事件觸發規則會雙擊事件優先;


關於多點觸摸事件(multiTouch)

多點觸摸事件相對照較複雜,此處僅僅討論 兩點觸摸.
多點觸摸事件的需要經過額外的方式進行檢測並處理事件,沒法與單點觸摸事件同樣直接event.getAction()獲得的就是相關的觸摸事件;

//分離觸摸事件,使用 MotionEvent.ACTION_MASK
//此方式可以正確分離出多點觸摸事件 ACTION_POINTER_X,也可以正常返回單點觸摸事件 ACTION_X
switch(event.getAction() & MotionEvent.ACTION_MASK){
    case MotionEvent.ACTION_X:
        //單點觸摸事件處理
        break;
    case MotionEvent.ACTION_POINTER_X:
        //多點觸摸事件處理
        brea;
}

兩點觸摸中的移動事件

首先,必須注意的一個點是:
多點觸摸事件中移動時觸發的移動事件也是ACTION_MOVE

也就是說ACTION_MOVE事件是移動的通用事件,在單點觸摸移動和多點觸摸移動中都存在.


兩點觸摸事件的觸發過程

除以上說起的共用ACTION_MOVE事件以外,多點觸摸事件可能存在的過程是這樣的:

  • [x] 狀況1
`ACTION_DOWN` -> `ACTION_POINTER_DOWN` -> `ACTION_MOVE` -> `ACTION_POINTER_UP` -> `ACTION_UP` 

這樣的狀況是在兩點觸摸時,兩個手指恰好 同一時候按上 -> 移動 -> 同一時候擡起

很是明顯,既然存在同一時候觸摸,也確定存在非同一時候觸摸了.當非同一時候觸摸時的過程是這樣的:

  • [x] 狀況2
`ACTION_DOWN` -> `ACTION_MOVE` -> `ACTION_POINTER_DOWN` -> `ACTION_MOVE` -> `ACTION_POINTER_UP` -> `ACTION_MOVE` -> `ACTION_UP`

這樣的狀況是先單點觸摸,觸發了ACTION_DOWN事件,而後第二個觸摸點按下時,觸發ACTION_POINTER_DOWN,而後當觸摸點擡起時,觸發ACTION_POINTER_UP(多個觸摸點的狀況下會屢次觸發ACTOIN_PONTER_DOWNACTION_POINTER_UP),以後單點觸摸擡起,觸發ACTION_UP;

  • 至於第一個ACTION_MOVE事件是否會觸發取決於第一個觸摸點與第二個觸摸點之間的時間間距(假設第二次按下的時間與第一次按下時間間隔足夠短,則不會觸發);

  • 同理第二個ACTION_MOVE取決於多點觸摸的按下與擡起的時間差,類似於單擊,多點觸摸按住時,ACTION_MOVE依舊是正常觸發,但距離值仍是 0.

  • 而第三個ACTION_MOVE是所有多點觸摸擡起後(僅僅剩下單點觸摸時),若還保持單點觸摸(不管有沒有移動)就會觸發第三輪的ACTION_MOVE事件

以上過程可以明白獲得:
不管是同一時候多點觸摸仍是間接多點觸摸,ACTION_DOWNACTION_UP兩個事件是必然會觸發並永遠在第一項和最後一項事件

因此,在處理多點觸摸的事件時,必須當心處理ACTION_MOVEACTION_UP事件,因爲這兩個事件並不是單點觸摸專屬的事件,而是所有的觸摸事件都會觸發的


兩點觸摸的事件

在兩點觸摸時,通常咱們不考慮兩點」單擊」(ACTION_POINTER_DOWN)事件,主要是針對兩點觸摸時觸發的移動事件進行處理;每每這樣的狀況需要處理的是類似放大/縮小的功能

  • 怎樣推斷兩點觸摸事件並處理

對於兩點觸摸事件,這個很是好推斷;當ACTION_POINTER_DOWN觸發時,說明觸發了兩點觸摸事件;當ACTION_POINTER_UP觸發時,說明兩點觸摸事件結束; 兩點觸摸事件主要是基於這兩個事件之間,難點在於:

怎樣區分單點觸摸的移動事件和兩點觸摸的移動事件

依據以上咱們肯定兩點觸摸時,會觸發ACTION_PONTER_DOWN事件,以後纔會觸發兩點觸摸事件的ACTION_MOVE,所以可以經過此事件肯定當前的ACTION_MOVE事件是否屬於兩點觸摸的仍是單點觸摸的事件

boolean isMultiDown=false;
switch(even.getAction() & MotionEvent.ACTION_MASK){
    case MotionEvent.ACTION_POINTER_DOWN:
        //記錄多點觸摸事件觸發
        isMultiDown=true;
        break;
    case MotionEvent.ACTION_MOVE:
        //檢測是否已經觸發了多點觸摸事件
        if(isMultiDown){
            //多點觸摸移動事件
        }else{
            //單點觸摸移動事件
        }
        break;
}

第二個可能的難點在於:

怎樣在ACTION_UP事件中區分並處理多點觸摸事件及單點觸摸事件

通常來講,處理多點觸摸事件時僅僅關注多點觸摸事件;處理單點觸摸事件時僅僅關注單點觸摸事件;而二者都存在的ACTION_UP事件而且都在最後,就可能形成一個沒必要要的麻煩:

可能在多點觸摸事件結束後,觸發的ACTION_UP事件處理了一次單點觸摸事件

而這可能會致使某些咱們不想要的狀況發生.因此關於ACTION_UP事件,咱們需要當心處理.固然,兩點觸摸的擡起事件是很是明白的ACTION_POINTER_UP,假設僅僅關心兩點觸摸事件時,就全然不需要再考慮ACTION_UP事件了.(只是在此輔助類中需要在up事件中回調單擊事件,因此這部分的處理仍是需要的.)


實現

依據以上的說明,大體的一個觸摸事件流程和需要響應的事件也已經肯定下來了.下面是整個觸摸事件及流程的一個實現思路,包含:

  • 變量定義
  • 觸摸事件處理流程

變量定義

因爲觸摸事件各類狀況相對複雜,先肯定需要處理的事件包含例如如下事件:

下面事件爲觸摸事件,說起觸摸事件特指下面五種系統反饋的觸摸事件,單擊/雙擊等非系統提早定義事件稱爲本身定義事件

  • ACTION_DOWN
  • ACTION_POINTER_DOWN
  • ACTION_POINTER_UP
  • ACTION_UP
  • ACTION_MOVE

需要的變量包含:

//是否單點觸摸按下,用於單擊事件的檢測
boolean isSingleDown;
//是否多點觸摸按下,用於區分處理事件
boolean isMultDown;
//多點觸摸按下的次數,相應多點觸摸的個數
//雖然咱們僅僅處理兩點觸摸,但實際可能多達N點觸摸
int multiTouchCount;
//是否進行了移動,不區分多點移動仍是單點移動
boolean isInMotionMove;
//是否進行了單點觸摸移動(優化處理單點觸摸與多點觸摸的切換)
boolean isSingleMove;
//是否完畢一次單擊事件
boolean isSingleClikEvent;

以上爲基本的需要變量,用於記錄各類不一樣的狀態以區分多點觸摸與單點觸摸及之間的單擊/雙擊/移動事件


觸摸事件流程

本身定義事件計時方案

首先需要知道的一個事情是:
關於單擊/雙擊事件中時間間隔的計時咱們經過Handler來處理

因爲Hanlder可以發送Delay的消息,咱們可以經過指定發送延時的消息交給Handler去取消事件或者消費事件;例如如下樣例:

Hanlder mHandler=new Handler{
    @Override
    public void handleMessage(Message msg){
        if(msg.what==CANCLE_EVENT){
            //處理相應的消息取消操做
        }
    }
}
//250ms後發送取消事件消息
mHandler.sendEmptyMessageDelayed(CANCLE_EVENT,250);

經過此方法,咱們可以在ACTION_DOWN事件觸發後設置一個按下的標識,而後發送一個延遲的取消按下事件消息,在ACTION_UP中直接檢測按下事件標識是否有效,有效則達成一次單擊事件,無效則說明已經超時,單擊事件沒法觸摸;

必須注意:不管ACTION_UP事件中按下標識是否有效,已經發生了ACTION_UP必然發生過ACTION_DOWN,按下識別是做爲單擊事件的檢測標識而不是ACTION_DOWN的觸發標識


本身定義事件觸發區域

對於以上說起的不一樣的觸摸事件中,不一樣的事件可能會觸發不一樣的本身定義事件

  • 基於時間的單擊事件:由ACTION_DOWN/ACTION_UP事件觸發
  • 基於距離的單擊事件:由ACTION_DOWN/ACTION_MOVE/ACTION_UP事件觸發
  • 雙擊事件:由ACTION_DOWN/ACTION_UP事件中被觸發

觸摸事件處理規則

  • 不論何時觸發雙擊事件再也不響應其餘事件(單擊或者UP等其餘事件)
  • 不論何時觸發多點觸摸事件則再也不響應單點觸摸的MOVE事件

對於每一個觸摸事件,除非被其餘事件消費或者攔截(如雙擊事件會攔截其餘興許事件),不然都會進行一次回調提供給子類進行處理,當中

  • ACTION_DOWN/ACTION_UP
    回調事件:onSingleTouchEventHandle(MotionEvent,int)

  • ACTION_POINTER_DOWN/ACTION_POINTER_UP
    回調事件:onMultiTouchEventHandle(MotionEvent,int)

  • ACTION_MOVE比較特殊,存在兩個回調可能

    1. 單點觸摸移動事件回調:
      onSingleTouchEventHandle(MotionEvent,int)
    2. 多點觸摸移動事件回調:
      onMultiTouchEventHandle(MotionEvent,int)

關於回調的方法

統一回調的方法分爲單點觸摸事件回調,多點觸摸事件回調;回調的時機是每一個相應的MotionEvent觸發時,在處理所有事件(單擊雙擊等)以後都會回調相應的事件以通知子類本身定義處理.

  • onSingleTouchEventHandle(MotionEvent, int)//單點觸摸事件回調
  • onMultiTouchEventHandle(MotionEvent, int)//多點觸摸事件回調

各事件的回調相應例如如下:

//省略參數
switch(event.getAction() & MotionEvent.MASK){
    case MotionEvent.ACTION_DOWN:
        onSingleTouchEventHandle();
        break;
    case MotionEvent.ACTION_POINTER_DOWN:
        onMultiTouchEventHandle();
        break;
    case MotionEvent.ACTION_UP:
        onSingleTouchEventHandle();
        break;
    case MotionEvent.ACTION_POINTER_UP:
        onMultiTouchEventHandle();
        break;
    case MotionEvent.ACTION_MOVE:
        //move事件是共用的,因此需要區分回調事件的類型
        onSingleTouchEventHandle() || onMultiTouchEventHandle();
        break;
}

參數意義:

  • 參數1爲: 觸摸事件
  • 參數2爲: 建議處理的觸摸事件類型

參數1很是好理解,僅僅是傳送了系統分發的觸摸事件變量而已,包含了觸摸點的座標,觸摸狀態等;
參數2是一個比較關鍵的參數;其存在是的意義是,建議以某個事件去處理當前的事件而不是直接按觸發的事件處理當前的事件.其使用的場景例如如下

因爲不論何時觸發了多點觸摸事件則再也不處理單點觸摸事件的MOVE事件(觸發規則)
因此當多點觸摸事件ACTION_POINTER_DOWN發生以後,所有的ACTION_MOVE轉爲多點觸摸的移動事件;
所以假設以前存在單點觸摸的ACTION_MOVE事件時,將結束該事件回調onSingleTouchEventHandle()並再也不處理;

所以,此時回調該事件時,將通知子類處理事件時建議處理爲ACTION_UP事件(因爲從這個時候開始整個單點觸摸事件已經結束了,以後也不會再響應不論什麼單點觸摸事件),視爲一次單點觸摸事件的結束


  • 注意事件

這裏回調時僅僅是建議而不會改動MotionEvent的事件參數,子類可以選擇忽略.
另外建議處理爲ACTION_UP的緣由是,單點觸摸事件結束的標誌是ACTION_UP,很是可能子類需要在這個時候處理某些數據或者保存工做.

因爲切換爲多點觸摸以後再也不響應單點觸摸事件,而終於事件結束時的ACTION_UP事件中的參數也很是可能與此時的參數不一致(最基本的就是觸摸點的座標了),所以此回建議處理爲ACTION_UP事件.

舉例:
如進行一次單點移動操做時,再按下一個觸摸點就變成了兩點觸摸事件.此時所有的事件將轉變成兩點觸摸事件,所以單點觸摸事件就結束了,回調action_up(建議的操做)通知相關的保存工做.
若不這樣處理,兩點觸摸中不論什麼的事件均可能影響到界面的變化,而且移動事件也變得不清晰了,因此在整個觸摸事件結束時纔回調action_up的話,很是有可能會讓單擊操做的很是多參數出現故障.

下面爲ACTION_MOVE事件中區分單點觸摸及多點觸摸事件的操做.

case ACTION_MOVE:
    //若已經觸發了多點觸摸事件且保持在多點觸摸狀態
    //當 multiTouchCount=0 時說明已經退了多點觸摸狀態,恢復到單點觸摸狀態
    //但以後依舊不會響應單點觸摸的 MOVE 事件
    if (mIsMultiDown && mMultiTouchCount > 0) {
        //若此前是單點觸摸的移動狀態時
        if (mIsSingleMove) {
            //按單點觸摸的結束狀態處理並再也不響應單點觸摸移動狀態
            showMsg("單擊 move 結束");
            //結束單點觸摸事件,並建議處理爲 UP 事件
            this.onSingleTouchEventHandle(event, MotionEvent.ACTION_UP);
            mIsSingleMove = false;
        }
        //正常直接多點移動操做
        showMsg("多點觸控 move");
        this.onMultiTouchEventHandle(event, MOTION_EVENT_NOTHING);
    }
break;

此處的實際應用場景在於:當界面被拖動移動時(依賴於ACTION_MOVE事件),切換到多點觸摸狀態時可以保證界面的正常(此時依賴於ACTION_UP事件保存移動後的位置)而不會在觸摸事件結束時再保存移動後位置,可能會發生忽然轉到移動到某個位置的狀況


觸摸事件處理源代碼

下面爲觸摸事件處理的完整流程(還有其餘的處理邏輯,但屬於輔助性的邏輯)

switch (event.getAction() & MotionEvent.ACTION_MASK) {
    case MotionEvent.ACTION_DOWN:
        //進入單點單擊處理
        showMsg("單點觸摸 down ");
        mIsSingleDown = true;
        //發送延遲取消按下標識消息
        mHandle.sendEmptyMessageDelayed(HANDLE_SINGLE_DOWN, SINGLE_CLICK_INTERVAL);

        //記錄按下座標
        mDownX = event.getX();
        mDownY = event.getY();
        this.onSingleTouchEventHandle(event, MOTION_EVENT_NOTHING);
        break;


    case MotionEvent.ACTION_POINTER_DOWN:
        //開始多點單擊事件
        showMsg("多點觸控 down");
        mIsMultiDown = true;
        //每發生一次多點觸摸此事件會觸發一次
        //經過此事件可以記錄多點觸摸的次數及推斷是否已經退出多點觸摸狀態(當變量爲0時)
        mMultiTouchCount += 1;
        this.onMultiTouchEventHandle(event, MOTION_EVENT_NOTHING);
        break;


    case MotionEvent.ACTION_UP:
        showMsg("單點觸摸 up");
        //不論什麼一種事件中,僅僅要觸發了雙擊事件,則結束事件
        //TODO: 雙擊事件檢測並處理,觸發 break;不然運行單點觸摸擡起事件

        //在處理單擊事件up中,不論何時僅僅要在結束up以前產生不論什麼的多點觸控,都不將這次的事件處理爲單點觸摸up
        //因爲這時候單點觸摸事件已經不完整了,混合了其餘的事件且多點觸摸可能致使本來的單點觸摸事件的座標數據不正常,因此再也不處理單點觸摸事件
        if (!mIsMultiDown && mMultiTouchCount <= 0) {
            //此處分爲兩種狀況
            //一種是未進行不論什麼多點觸摸狀態的,那麼必然爲單,事件必須響應
            //在事件響應處兩個推斷條件是:1.用戶高速單擊,沒有move事件,此時 isInMotionMove=false;
            if (!mIsInMotionMove
                    //2. 用戶慢速單產生了move事件但仍沒有形成多點觸摸事件,此時 isInMotionMove=true 且 isSingleMove=true;
                    || (mIsInMotionMove && mIsSingleMove)) {
                showMsg("單擊 up");
                this.onSingleTouchEventHandle(evenMOTION_EVENT_NOTHING);
            } else {
        //一種是進行了多點觸摸,且在多點觸摸結束以後保持單點觸摸的狀態,此時以多點觸摸按下的時刻處理觸摸事件(即在move中已經按up處理掉事件了)
        //則在完畢所有事件以後的up中將再也不處理該事事件,即下面的"不處理"
                showMsg("單擊 up 不處理");
            }

        //處理觸摸結束事件,重置變量
        this.finishTouchEvent();
        break;


    case MotionEvent.ACTION_POINTER_UP:
        //當確認進入多點單擊狀態,則運行多點單擊擡起事件
        if (mMultiTouchCount > 0) {
            showMsg("多點觸控 up");
            this.onMultiTouchEventHandle(event, MOTION_EVENT_NOTHING);
        }
        //每次多點觸摸擡起觸發一次,多點觸摸次數-1(直到僅僅剩單點觸摸爲止再也不觸發此事件,此時變量值爲0)
        mMultiTouchCount -= 1;
        break;


    case MotionEvent.ACTION_MOVE:
        //進入移動狀態
        mIsInMotionMove = true;
        //當前不是多點單擊狀態,則進行移動操做
        //若觸發了多點觸摸事件,則結束單點移動事件,進入多點觸摸移動事件

        //結束單點移動操做後在觸摸事件結束以前都不會再運行單點移動操做
        //這樣的狀況是爲了不有可能實用戶單擊移動以後再進行多點觸控,這樣的狀況沒法處理爲用戶需要移動仍是需要縮放
        //而且引發的座標變化可能致使一些錯亂
        if (!mIsMultiDown && mMultiTouchCount <= 0) {
            showMsg("單點觸摸 move");
            this.onSingleTouchEventHandle(event, MOTION_EVENT_NOTHING);
            mIsSingleMove = true;
            //多點觸摸事件觸發了,進入多點觸摸移動事件
        } else if (mIsMultiDown && mMultiTouchCount > 0) {
            //若此前是單點觸摸的移動狀態時
            if (mIsSingleMove) {
                //按單點觸摸的結束狀態處理並再也不響應單點觸摸移動狀態
                showMsg("單擊 move 結束");
                this.onSingleTouchEventHandle(event, MotionEvent.ACTION_UP);
                mIsSingleMove = false;
            }
            //正常直接多點移動操做
            showMsg("多點觸控 move");
            this.onMultiTouchEventHandle(event, MOTION_EVENT_NOTHING);
        }
        break;
}

雙擊事件的優化處理

前面提到,關於雙擊事件的處理邏輯是這樣的:完畢一次單擊事件記錄單擊事件標識,第二次觸發單擊事件時,依據此前是否存在第一次單擊事件來肯定是否觸發雙擊事件

在這裏需要注意的一個點是,單擊事件存在兩種方式,而每一種方式的完畢都是一次單擊事件.
咱們定義的雙擊事件是:

兩次必定時間間隔內連續發生的單擊事件即爲一次雙擊事件,這裏單擊事件的觸發方式是隨意的

而因爲單擊事件在ACTION_UP事件中檢測並觸發,兩種方式的單擊事件都需要檢測及處理,因此這個過程可能致使單擊事件會被觸發兩次

  • 基於時間的單擊事件觸發一次
  • 基於距離的單擊事件觸發一次

這樣的狀況下就需要用不一樣的變量來識別單擊事件的觸發了.需要處理的包含:

  • 是否已經觸發了一次單擊事件(即上一次單擊事件是否還在有效時間間隔內)
  • 當次觸摸事件中是否觸發了單擊事件(不管該事件由哪一種方式觸發)

怎樣檢測當次觸摸事件的單擊事件

因爲兩種方式的單擊事件都需要檢測一次,因此可能存在一種狀況:(不管哪一種方式優先檢測)

條件:假設單擊事件成立標識爲true
狀況: -----
1.假設已經進行了一次單擊,最後一次單擊標識爲true.
2.首先檢測第一種單擊方式(先檢測是否觸發雙擊事件),當第一種方式單擊成立以後,本次觸摸事件單擊事件成功,標識置爲true;
3.當另一種方式檢測時(先檢測是否觸發雙擊事件),最後一次單擊標識爲true,且本次觸摸事件已經爲true(上一個方式已經肯定),則會觸發一次雙擊事件 -----
結果:在一次單擊事件檢測中,因爲兩種單擊方式都需要檢測就可能致使一次單擊事件就會觸發雙擊事件了.
這個過程明顯是存在BUG

所以需要設置不一樣標識在不一樣方式的單擊事件中使用;
固然,現在已經加入了兩種檢測方式僅僅能觸發一種的處理,因此這個問題easy處理很是多.

//是否觸發基於時間的單擊事件
boolean isFireTimeClickEvent=false;
//是否觸發基於距離的單擊事件
boolean isFireDistanceClickEvent=false;
//上一次單擊事件是否被觸發了
boolean isFireLastClickEvent=false;

怎樣檢測觸發雙擊事件

雙擊事件是基於單擊事件的,兩次連續觸發的單擊事件才構成一次雙擊事件;所以雙擊事件的檢測(這裏需要注意事件的檢測與事件的響應是分開的,檢測到某個事件不必定需要響應相應的事件):

  • 必須在單擊事件檢測以後才檢測雙擊事件(觸發了單擊事件纔可能構成一次雙擊事件)
  • 雙擊事件的檢測必須優先於單擊事件的響應(一旦觸發雙擊事件再也不響應單擊事件)
  • 一旦雙擊事件被觸發,則忽略其餘所有事件(也再也不響應單擊事件)

不論什麼一次單擊事件檢測完畢以後都需要檢測雙擊事件,再運行單擊事件;單擊事件需要檢測兩次(因爲有兩種觸發方式),所以雙擊事件是必須檢測兩次的.
雙擊事件被觸發時,其餘事件ACTON_UP事件是不會觸發的

//檢測是否觸發雙擊事件
//檢測本次觸摸事件中是否觸發了(不論什麼一種)單擊事件
if((isFireTimeClickEvent || isFireDistanceClickEvent) 
    //檢測上一次單擊事件是否觸發了
    && isFireLastClickEvent){
    //條件成立,觸發雙擊事件
    //同一時候重置所有相關變量
    //因爲雙擊事件已經觸發,保留變量狀態會影響下一次推斷
    isFireTimeClickEvent=false;
    isFireDistanceClickEvent=false;
    isFireLastClickEvent=false;
    //處理了雙擊事件則再也不響應不論什麼其餘事件
    break;//或者return;
}
//若處理了時間單擊事件,相應標識置爲true
isFireTimeClickEvent=true;

//同上檢測雙擊事件
//若處理了距離單擊事件,相應標識置爲true
isFireDistanceClickEvent=true;

//保存這次單擊狀態
isFireLastClickEvent = isFireTimeClickEvent || isFireDistanceClickEvent;

輔助補充邏輯

在以上的觸摸處理事件中,咱們提到單擊分爲兩中方式:

  • 基於時間的單擊事件
  • 基本距離的單擊事件

基於時間的單擊事件處理已經在ACTION_DOWN事件中操做了,經過延時發送取消按下標識,再從ACTION_UP事件中進行推斷是否處理爲一次基於時間的單擊事件
基於距離的單擊事件在ACTION_DOWN中沒有不論什麼處理,因爲距離自己跟時間沒有不論什麼關係.

  • [x] 基於距離的單擊事件
    在單點觸摸中按下以後,保持不論什麼時間(甚至無窮長),僅僅要在ACTION_UP事件中擡起的座標與按下座標值距離在贊成範圍內即爲一次基於距離的單擊事件

因爲存在這樣的特殊的單擊方式,因此基於距離的單擊事件僅僅跟ACTION_DOWN/ACTION_UP有關;但這是不無缺的.

存在一種可能,**在單點觸摸中按下以後,觸摸點進行了移動`ACTION_MOVE`事件,而後再移動回到按下的位置**
即`ACTION_DOWN`的位置座標,此時在`ACTION_UP`事件中,觸摸點的座標沒有變化,`ACTION_MOVE`中所有的操做對`ACTION_UP`是透明無效的,這可能會違背了咱們需要處理的單擊事件

查看單擊事件的兩種方式

因此需要修正這樣的可能存在的錯誤;修正方式也很是easy,即然是在ACTION_MOVE中產生的問題,在ACTION_MOVE修正;
修正方式例如如下,針對距離單擊事件

//事先定義用於標識觸摸點是否產生超過單擊的贊成範圍的移動事件(下面稱爲非法事件)
//**針對距離單擊事件**
booelan mIsClickDistanceMove = false;
case MotionEvent.ACTION_MOVE:
    //當前移動過程當中,觸摸點未產生非法移動事件
    if (!mIsClickDistanceMove) {
        //進行檢測
        float moveDistanceX = event.getX() - mUpX;
        float moveDistanceY = event.getY() - mUpY;
        //此處爲移動贊成的偏移量範圍,因爲手指easy抖動,增大此值可以增大容錯率
        int offsetDistance = SINGLE_CLICK_OFFSET_DISTANCE;
        //觸摸點移動超過單擊贊成範圍的偏移量
        if (Math.abs(moveDistanceX) > offsetDistance
                || Math.abs(moveDistanceY) > offsetDistance) {
            //產生非法移動事件
            //一旦產生了非法移動事件,則不需要再次檢測了
            mIsClickDistanceMove = true;
        }
    }

小結

以上爲所有的觸摸事件的處理方案

  • 主要解決的事件有:單擊/雙擊/移動及所有觸摸事件相應的回調
  • 當中單擊事件分爲:
    • 基於時間的單擊事件
    • 基於距離的單擊事件
  • 調整和修正各個事件之間的衝突關係

使用方式

直接實例化TouchEventHelper,實現其接口,重寫所有抽象方法就能夠.
TouchEventHelper實現了View.onTouchListnener接口,經過此監聽方法實現對觸摸事件的操做.
在Activity中也可以使用,調用時onTouch(View,MotionEvent)傳遞的參數中將view設置爲null就能夠.
整個觸摸事件處理中並不會涉及不論什麼跟view有關的操做,僅分析及處理MotionEvent

//參數爲事件處理接口 onTouchEventListener
TouchEventHelper helper=new TouchEventHelper(this);
//直接在需要處理觸摸事件的地方調用onTouch方法,如:
//onTouchEvent(MotionEvent event),在view中
//dispatchTouchEvent(MotionEvent event),在view,activity或者viewGroup中
helper.onTouch(null,event);
//或者直接使用view.setOnTouchListener(helper)
//activity中沒法使用setOnTouchListener(),因此僅僅能在相應的方法中進行調用

源代碼

源代碼請移步Github查看;


GitHub地址

https://github.com/CrazyTaro/TouchEventHandle

回到文件夾

相關文章
相關標籤/搜索