處理觸控事件與其優先權

onTouchEvent是View類別提供的方法之一,主要用來處理「觸控」事件,我們只要繼承View,就可以將其覆寫並設計自己需要的功能。覆寫onTouchEvent的時後,必須將MotionEvent這個物件帶入,使其能夠找到對應的Case,然後執行相對應的動作。

簡單的範例程式碼如下所示:

@Override
public boolean onTouchEvent(MotionEvent event) {

    
    paint.setColor(Color.WHITE);
    paint.setStyle(Paint.Style.FILL);


    switch(event.getAction())
    {

      case MotionEvent.ACTION_MOVE:
          canvas.drawLine(mov_x, mov_y, event.getX(), event.getY(), paint);
          invalidate();
          break;
      case MotionEvent.ACTION_DOWN:
          mov_x=(int) event.getX();
          mov_y=(int) event.getY();
          canvas.drawPoint(mov_x, mov_y, paint);
          invalidate();
          break;

      ......
      ......
    }
    return true;
}

MotionEvent是一個用來回報觸控動作(滑鼠,觸控筆,手指)的物件,其定義了許多觸控事件的類型和方法,常見的有下列幾項:

01.ACTION_UP:按壓的手勢結束
02.ACTION_DOWN:按壓的手勢開始
03.ACTION_POINTER_UP:非主要點抬起(離開面板)
04.ACTION_POINTER_DOWN:非主要點下來(觸及面板)
05.ACTION_MOVE:按壓的過程當中位置有變化(例如:手指在屏幕上滑動)
06.getX():回傳X座標
07.getY():回傳Y座標
08.getSize():回傳觸控點的尺寸(回傳值介於0和1之間)
09.getPressure():回傳觸控壓力大小(回傳值介於0和1之間)
10.getPointerId():回傳觸控點的ID(每一個觸控點的ID,在ACTION_UP完成之前是唯一值)

在Android作業系統,任何輸入(MotionEvent, KeyEvent, TrackEvent)都會先存放至EventHub,接著WindowManagerServicec會透過InputManager所提供的介面來啟動一個執行緒(Thread)執行InputReader,InputReader裡面的looponce方法會去呼叫mEventHub->getEvents(),取得最原始的RawEvent,再經由InputDispatcher分類並分發......(略),有興趣的朋友可以參考下列文章。

參考資料:
Android的KeyEvent:從EventHub到PhoneWindowManager
Android輸入事件流程中的EventHub分析

簡而言之,這些觸控事件都是從Android的底層,經過一層層關卡向上回報至Framework,讓上層的應用程式和Framework可以針對不同的情況做出調整。

那本篇文章的主題「處理觸控事件的優先權」,泰勞先說明一下我實作的情境背景,我遇到的問題跟「觸控筆」有關,簡單的說就是希望當觸控筆觸及屏幕時,能夠以觸控筆為優先,並且在過程中避免受到其他新加入觸控事件的干擾。

其實這有很多開發上的限制,舉例來說,觸控筆本身沒有發出任何訊號,裝置跟筆之間無法溝通,因此屏幕無法精準判斷是否為觸控筆。像是三星的Note系列,在觸控筆這一塊就設計的不錯,只要觸控筆「接近」屏幕時,螢幕會出現焦點,此時任何手指觸及屏幕都是沒有作用的。

在一般裝置上,我能想到的方法只有先經由getSize()粗略的區分手指和觸控筆,再透過getPointerId()來針對特定觸及點做客製,盡可能的去實現觸控筆優先的策略。

註:以下範例要感謝好夥伴Sam和Annie的幫忙與指導。

簡單的範例程式碼如下所示:

@Override
public boolean onTouchEvent(MotionEvent event){

    float inputSize = event.getSize();
    int index = event.getActionIndex();
    int inputId = event.getPointerID(index);
    boolean isStylus = false;
    int stylusId = -1;

    switch(event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            //觸控筆的Size回傳值為0.02
            if(inputSize==0.02 && isStylus==false){
                isStylus = true;
                stylusId = inputId;  //紀錄觸控筆的PointerId
            }
            if((isStylus==true) && (inputId==stylusId)){
                mov_x = event.getX();
                mov_y = event.getY();
                //If the new input event is stylus, then move to new coordinate.
                mPath.moveTo(mov_x,mov_y);
            }
            ......
            break;
        case MotionEvent.ACTION_MOVE:
            ......
    }
}

上面特別註明觸控筆的Size為0.02,其實經過多次的測試結果發現,雖然手指的Size大約落在0.06~0.899之間,但是在手指幾乎要離開屏幕的瞬間或是使用指甲觸摸,都有可能會出現0.02左右的Size,很容易造成誤判。

雖然此方法可以實作出效果,但並沒有辦法精準判斷,就當作是一個練習吧! 

留言

這個網誌中的熱門文章

Repo 實用指令

什麼是 Bootloader?

程式語言常用之符號與詞彙 - 中英文對照