當使用者將一或多根手指放在觸控螢幕上,且應用程式將這種觸控模式解讀為手勢時,就會發生觸控手勢。手勢偵測分為兩個階段:
- 收集觸控事件資料。
- 解讀資料,判斷是否符合應用程式支援的手勢條件。
AndroidX 類別
本文中的範例使用 GestureDetectorCompat 和 MotionEventCompat 類別。這些類別位於 AndroidX 程式庫。請盡可能使用 AndroidX 類別,確保與舊版裝置相容。MotionEventCompat「」MotionEvent類別的「」MotionEvent類別。而是提供靜態公用程式方法,您可將 MotionEvent 物件傳遞至這些方法,接收與該事件相關聯的動作。
收集資料
當使用者將一或多根手指放在畫面上時,系統會觸發接收觸控事件的檢視區塊回呼 onTouchEvent()。對於每個觸控事件序列 (例如位置、壓力、大小和新增另一根手指),如果系統將其識別為手勢,onTouchEvent() 就會觸發多次。
手勢會在使用者首次觸控螢幕時開始,並在系統追蹤使用者手指位置時持續,最後在擷取使用者最後一根手指離開螢幕的最終事件時結束。在整個互動過程中,傳送至 onTouchEvent() 的 MotionEvent 會提供每次互動的詳細資料。應用程式可以使用 MotionEvent 提供的資料,判斷是否發生感興趣的手勢。
擷取 Activity 或 View 的觸控事件
如要在 Activity 或 View 中攔截觸控事件,請覆寫 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_MOVE 和 ACTION_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 會為所有 on<TouchEvent> 方法提供實作方式,並為所有方法傳回 false。這樣您就只會覆寫自己關心的方法。舉例來說,下列程式碼片段會建立擴充 GestureDetector.SimpleOnGestureListener 的類別,並覆寫 onFling() 和 onDown()。
無論使用 GestureDetector.OnGestureListener 或 GestureDetector.SimpleOnGestureListener,最佳做法都是實作會傳回 true 的 onDown() 方法。這是因為所有手勢都會以 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; } } }