偵測常用手勢

試用 Compose
Jetpack Compose 是 Android 推薦的 UI 工具包。瞭解如何在 Compose 中使用觸控和輸入功能。

當使用者將單指或多指放在觸控螢幕上,且應用程式將這種觸控模式解讀為手勢時,就會發生一次觸控手勢。手勢偵測分為兩個階段:

  1. 收集觸控事件資料。
  2. 解讀資料,判斷資料是否符合應用程式支援的手勢條件。

AndroidX 類別

本文件中的範例使用 GestureDetectorCompatMotionEventCompat 類別。這些類別位於 AndroidX 程式庫中。請盡可能使用 AndroidX 類別,以提供與舊版裝置的相容性。MotionEventCompat「不會」取代 MotionEvent 類別。相反地,它會提供靜態公用程式方法,您傳遞 MotionEvent 物件以接收與該事件相關聯的動作。

收集資料

當使用者將一根手指放在螢幕上時,這會在接收觸控事件的檢視畫面中觸發回呼 onTouchEvent()。對於每個視為手勢的觸控事件序列 (例如位置、壓力、大小和新增手指新增動作),系統會多次觸發 onTouchEvent()

這個手勢會在使用者首次輕觸螢幕時開始,持續追蹤使用者的手指或手指位置,直到使用者最後的手指離開螢幕時,擷取到最終事件。透過此互動,傳送到 onTouchEvent()MotionEvent 會提供每次互動的詳細資料。應用程式可以使用 MotionEvent 提供的資料,判斷重要的手勢是否發生。

擷取活動或檢視畫面的觸控事件

如要攔截 ActivityView 中的觸控事件,請覆寫 onTouchEvent() 回呼。

下列程式碼片段使用 getAction(),從 event 參數擷取使用者執行的動作。如此一來,您就能取得所需的原始資料,以判斷您關注的手勢是否出現。

Kotlin

class MainActivity : Activity() {
    ...
    // This example shows an Activity. You can use the same approach if you are 
    // subclassing a View.
    override fun onTouchEvent(event: MotionEvent): Boolean {
        return when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                Log.d(DEBUG_TAG, "Action was DOWN")
                true
            }
            MotionEvent.ACTION_MOVE -> {
                Log.d(DEBUG_TAG, "Action was MOVE")
                true
            }
            MotionEvent.ACTION_UP -> {
                Log.d(DEBUG_TAG, "Action was UP")
                true
            }
            MotionEvent.ACTION_CANCEL -> {
                Log.d(DEBUG_TAG, "Action was CANCEL")
                true
            }
            MotionEvent.ACTION_OUTSIDE -> {
                Log.d(DEBUG_TAG, "Movement occurred outside bounds of current screen element")
                true
            }
            else -> super.onTouchEvent(event)
        }
    }
}

Java

public class MainActivity extends Activity {
...
// This example shows an Activity. You can use the same approach if you are
// subclassing a View.
@Override
public boolean onTouchEvent(MotionEvent event){
    switch(event.getAction()) {
        case (MotionEvent.ACTION_DOWN) :
            Log.d(DEBUG_TAG,"Action was DOWN");
            return true;
        case (MotionEvent.ACTION_MOVE) :
            Log.d(DEBUG_TAG,"Action was MOVE");
            return true;
        case (MotionEvent.ACTION_UP) :
            Log.d(DEBUG_TAG,"Action was UP");
            return true;
        case (MotionEvent.ACTION_CANCEL) :
            Log.d(DEBUG_TAG,"Action was CANCEL");
            return true;
        case (MotionEvent.ACTION_OUTSIDE) :
            Log.d(DEBUG_TAG,"Movement occurred outside bounds of current screen element");
            return true;
        default :
            return super.onTouchEvent(event);
    }
}

當使用者輕觸、輕觸、按住並拖曳時,這個程式碼會在 Logcat 中產生類似以下的訊息:

GESTURES D   Action was DOWN
GESTURES D   Action was UP
GESTURES D   Action was MOVE

針對自訂手勢,您可以對這些事件自行處理,決定這些事件是否代表您需要處理的手勢。不過,如果應用程式使用常見的手勢 (例如輕觸兩下、按住、快速滑過等),則可利用 GestureDetector 類別。GestureDetector 可讓您更輕鬆地偵測常用手勢,而不必自行處理個別觸控事件。這會在偵測手勢一節中進一步討論。

擷取單一檢視畫面的觸控事件

除了 onTouchEvent() 之外,您也可以使用 setOnTouchListener() 方法,將 View.OnTouchListener 物件附加至任何 View 物件。如此一來,即可在不將現有 View 建立子類別的情況下,監聽觸控事件,如以下範例所示:

Kotlin

findViewById<View>(R.id.my_view).setOnTouchListener { v, event ->
    // Respond to touch events.
    true
}

Java

View myView = findViewById(R.id.my_view);
myView.setOnTouchListener(new OnTouchListener() {
    public boolean onTouch(View v, MotionEvent event) {
        // Respond to touch events.
        return true;
    }
});

請務必為 ACTION_DOWN 事件建立會傳回 false 的事件監聽器。如果這麼做,後續的 ACTION_MOVEACTION_UP 事件序列就不會呼叫事件監聽器。這是因為所有觸控事件的起點都是 ACTION_DOWN

如果要建立自訂檢視畫面,可以按照前文所述覆寫 onTouchEvent()

偵測手勢

Android 提供用於偵測常見手勢的 GestureDetector 類別。支援的部分手勢包括 onDown()onLongPress()onFling()。您可以將 GestureDetector 與前述的 onTouchEvent() 方法搭配使用。

偵測所有支援的手勢

GestureDetectorCompat 物件執行個體化時,需要的參數之一是實作 GestureDetector.OnGestureListener 介面的類別。發生特定觸控事件時,GestureDetector.OnGestureListener 會通知使用者。如要讓 GestureDetector 物件能接收事件,請覆寫檢視畫面或活動的 onTouchEvent() 方法,並將所有觀察到的事件傳遞至偵測工具執行個體。

在下列程式碼片段中,個別 on<TouchEvent> 方法的 true 傳回值表示觸控事件已處理。false 的傳回值會透過檢視堆疊向下傳遞事件,直到觸控成功處理為止。

如果您在測試應用程式中執行以下程式碼片段,就能瞭解與觸控螢幕互動時觸發動作的方式,以及每個觸控事件的 MotionEvent 內容。接著,您會看到系統為簡易互動產生多少資料。

Kotlin

private const val DEBUG_TAG = "Gestures"

class MainActivity :
        Activity(),
        GestureDetector.OnGestureListener,
        GestureDetector.OnDoubleTapListener {

    private lateinit var mDetector: GestureDetectorCompat

    // Called when the activity is first created.
    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Instantiate the gesture detector with the
        // application context and an implementation of
        // GestureDetector.OnGestureListener.
        mDetector = GestureDetectorCompat(this, this)
        // Set the gesture detector as the double-tap
        // listener.
        mDetector.setOnDoubleTapListener(this)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        return if (mDetector.onTouchEvent(event)) {
            true
        } else {
            super.onTouchEvent(event)
        }
    }

    override fun onDown(event: MotionEvent): Boolean {
        Log.d(DEBUG_TAG, "onDown: $event")
        return true
    }

    override fun onFling(
            event1: MotionEvent,
            event2: MotionEvent,
            velocityX: Float,
            velocityY: Float
    ): Boolean {
        Log.d(DEBUG_TAG, "onFling: $event1 $event2")
        return true
    }

    override fun onLongPress(event: MotionEvent) {
        Log.d(DEBUG_TAG, "onLongPress: $event")
    }

    override fun onScroll(
            event1: MotionEvent,
            event2: MotionEvent,
            distanceX: Float,
            distanceY: Float
    ): Boolean {
        Log.d(DEBUG_TAG, "onScroll: $event1 $event2")
        return true
    }

    override fun onShowPress(event: MotionEvent) {
        Log.d(DEBUG_TAG, "onShowPress: $event")
    }

    override fun onSingleTapUp(event: MotionEvent): Boolean {
        Log.d(DEBUG_TAG, "onSingleTapUp: $event")
        return true
    }

    override fun onDoubleTap(event: MotionEvent): Boolean {
        Log.d(DEBUG_TAG, "onDoubleTap: $event")
        return true
    }

    override fun onDoubleTapEvent(event: MotionEvent): Boolean {
        Log.d(DEBUG_TAG, "onDoubleTapEvent: $event")
        return true
    }

    override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
        Log.d(DEBUG_TAG, "onSingleTapConfirmed: $event")
        return true
    }

}

Java

public class MainActivity extends Activity implements
        GestureDetector.OnGestureListener,
        GestureDetector.OnDoubleTapListener{

    private static final String DEBUG_TAG = "Gestures";
    private GestureDetectorCompat mDetector;

    // Called when the activity is first created.
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Instantiate the gesture detector with the
        // application context and an implementation of
        // GestureDetector.OnGestureListener.
        mDetector = new GestureDetectorCompat(this,this);
        // Set the gesture detector as the double-tap
        // listener.
        mDetector.setOnDoubleTapListener(this);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event){
        if (this.mDetector.onTouchEvent(event)) {
            return true;
        }
        return super.onTouchEvent(event);
    }

    @Override
    public boolean onDown(MotionEvent event) {
        Log.d(DEBUG_TAG,"onDown: " + event.toString());
        return true;
    }

    @Override
    public boolean onFling(MotionEvent event1, MotionEvent event2,
            float velocityX, float velocityY) {
        Log.d(DEBUG_TAG, "onFling: " + event1.toString() + event2.toString());
        return true;
    }

    @Override
    public void onLongPress(MotionEvent event) {
        Log.d(DEBUG_TAG, "onLongPress: " + event.toString());
    }

    @Override
    public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
            float distanceY) {
        Log.d(DEBUG_TAG, "onScroll: " + event1.toString() + event2.toString());
        return true;
    }

    @Override
    public void onShowPress(MotionEvent event) {
        Log.d(DEBUG_TAG, "onShowPress: " + event.toString());
    }

    @Override
    public boolean onSingleTapUp(MotionEvent event) {
        Log.d(DEBUG_TAG, "onSingleTapUp: " + event.toString());
        return true;
    }

    @Override
    public boolean onDoubleTap(MotionEvent event) {
        Log.d(DEBUG_TAG, "onDoubleTap: " + event.toString());
        return true;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent event) {
        Log.d(DEBUG_TAG, "onDoubleTapEvent: " + event.toString());
        return true;
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent event) {
        Log.d(DEBUG_TAG, "onSingleTapConfirmed: " + event.toString());
        return true;
    }
}

偵測支援手勢的子集

如果只想處理幾個手勢,可以擴充 GestureDetector.SimpleOnGestureListener,而非實作 GestureDetector.OnGestureListener 介面。

GestureDetector.SimpleOnGestureListener藉由傳回所有方法的 false,實作所有 on<TouchEvent> 方法的實作。這可讓您只覆寫您重視的方法。舉例來說,下列程式碼片段會建立可擴充 GestureDetector.SimpleOnGestureListener 的類別,並覆寫 onFling()onDown()

無論使用 GestureDetector.OnGestureListener 還是 GestureDetector.SimpleOnGestureListener,最佳做法都是實作會傳回 trueonDown() 方法。這是因為所有手勢的開頭都是 onDown() 訊息。如果您從 onDown() 傳回 false,就像預設 GestureDetector.SimpleOnGestureListener 一樣,系統會假設您要忽略手勢的其餘部分,而並未呼叫 GestureDetector.OnGestureListener 的其他方法。這可能會導致應用程式發生未預期的問題。只有在確實要忽略整個手勢時,才從 onDown() 傳回 false

Kotlin

private const val DEBUG_TAG = "Gestures"

class MainActivity : Activity() {

    private lateinit var mDetector: GestureDetectorCompat

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mDetector = GestureDetectorCompat(this, MyGestureListener())
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        mDetector.onTouchEvent(event)
        return super.onTouchEvent(event)
    }

    private class MyGestureListener : GestureDetector.SimpleOnGestureListener() {

        override fun onDown(event: MotionEvent): Boolean {
            Log.d(DEBUG_TAG, "onDown: $event")
            return true
        }

        override fun onFling(
                event1: MotionEvent,
                event2: MotionEvent,
                velocityX: Float,
                velocityY: Float
        ): Boolean {
            Log.d(DEBUG_TAG, "onFling: $event1 $event2")
            return true
        }
    }
}

Java

public class MainActivity extends Activity {

    private GestureDetectorCompat mDetector;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mDetector = new GestureDetectorCompat(this, new MyGestureListener());
    }

    @Override
    public boolean onTouchEvent(MotionEvent event){
        if (this.mDetector.onTouchEvent(event)) {
              return true;
        }
        return super.onTouchEvent(event);
    }

    class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
        private static final String DEBUG_TAG = "Gestures";

        @Override
        public boolean onDown(MotionEvent event) {
            Log.d(DEBUG_TAG,"onDown: " + event.toString());
            return true;
        }

        @Override
        public boolean onFling(MotionEvent event1, MotionEvent event2,
                float velocityX, float velocityY) {
            Log.d(DEBUG_TAG, "onFling: " + event1.toString() + event2.toString());
            return true;
        }
    }
}

其他資源