2014年1月12日 星期日

[Android] 處理多點觸碰

以前設計的Android app大多是使用系統提供的元件,就算要客制化,也是繼承現有的class,依自已的需求去Override function。

今天要寫的是自已處理多點觸碰的功能,繼承android.view.View,覆寫onTouchEvent。



普通要辯使user multitouch的行為的話,有更方便的Gesture可以用,但今天不討論這一塊。

簡略的onTouchEvent覆寫:

@Override
 public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {  
        case MotionEvent.ACTION_DOWN:
          //第一個觸碰點觸碰螢幕時觸發
        case MotionEvent.ACTION_POINTER_DOWN:
          //第二個以上觸碰點觸碰螢幕時觸發
        case MotionEvent.ACTION_MOVE:
          //只要在螢幕上的觸碰點有移動時就會觸發
        case MotionEvent.ACTION_POINTER_UP:
          //觸碰點離開時,且螢幕上還有其他觸碰點時觸發
        case MotionEvent.ACTION_UP:
          //最後一個觸碰點離開螢幕時觸發
        }  
        return true;  
 }


在一個完整的Multitouch中,每個觸碰點(pointer)都有一個獨一無二的ID,我們可以借由 event.getX(int)、 event.getY(int) 來取得觸碰點(pointer)的x、y座標。getX和getY裡面傳的int是pointer ID。

但pointer ID是android自動assign的,我們怎麼知道pointer ID是多少呢?
原來event裡面就告訴我們啦

        int index = event.getActionIndex();
        int ID = event.getPointerId(index);

第一行告訴我們現在是哪一個index的觸碰點產生的事件
第二行就是根據這個index去查它的ID是多少

有了這些工具,我們就能偵測user什麼時候手指碰到螢幕、划動、離開,及動作的x、y位置了!

最後,只要把程式邏輯寫出來,就可以實作多點觸控了!

例如:兩指的縮放功能的演算法:

@Override
 public boolean onTouchEvent(MotionEvent event) { 
        int index = event.getActionIndex();
        int ID = event.getPointerId(index);
         
        switch (event.getActionMasked()) {  
        case MotionEvent.ACTION_DOWN:
        case MotionEvent.ACTION_POINTER_DOWN:
         //紀錄新加入的點的ID、xy座標
         //紀錄此時所有在螢幕上的pointer的中心
         //紀錄此時所有在螢幕上的pointer所圍成的面積
         //紀錄此時target的原xy座標、原寬及高
        case MotionEvent.ACTION_MOVE: 
         //計算新的所有pointer的中心
         //計算新的所有pointer的面積
         //計算位移量 = 新的中心 - 原中心
         //計算縮放量 = 新的面積 - 原面積
         //將target的位置移到位移量、大小放到縮放量去
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_POINTER_UP:
         //將離開的點從紀錄中刪掉
         //更新目前所有還在螢幕上的所有pointer的中心
         //更新目前所有還在螢幕上的所有pointer所圍成的面積
         //更新此時target的xy座標、寬高
        }  
        return true;  
 }

後記:這次讓我花最多時間的是…如果將移動、縮放功能的detection直接寫在target view上的話,且在MotionEvent.ACTION_MOVE的時候直接去update target的話,那麼之後所有抓到的getX() getY() 都會不是預期的。因為getX()和 getY() 抓的是相對座標,是相對於那個target的。在移的過程中若把target的位置移走了,那麼未來抓到的getX()和getY()就不會是相對於原本那個座標了。
解決方法是在MotionEvent.ACTION_MOVE時,先add一個假的view,所有移動、縮放都作用在這個假view上,這樣原view不動,且touch event會繼續作用在原view上!就不會影響座標了 :)

以後有空的話再把完整的source code整理放上來 ~

沒有留言:

張貼留言