当用户将一根或多根手指放在触摸屏上,并且你的应用将此触摸模式解释为手势时,就会发生 _触摸手势_。手势检测有两个阶段
- 收集触摸事件数据。
- 解释数据以确定它是否符合你的应用支持的手势的标准。
AndroidX 类
本文档中的示例使用 GestureDetectorCompat
和 MotionEventCompat
类。这些类位于 AndroidX 库 中。尽可能使用 AndroidX 类以提供与早期设备的兼容性。 MotionEventCompat
_不是_ MotionEvent
类的替代品。相反,它提供了静态实用程序方法,您可以将 MotionEvent
对象传递给这些方法以接收与该事件关联的操作。
收集数据
当用户将一根或多根手指放在屏幕上时,这将触发接收触摸事件的视图上的回调 onTouchEvent()
。对于每个被识别为手势的触摸事件序列(例如位置、压力、大小以及另一根手指的添加),onTouchEvent()
会被触发多次。
手势从用户首次触摸屏幕开始,在系统跟踪用户手指或手指的位置时继续,并通过捕获用户最后离开屏幕的手指的最终事件结束。在整个交互过程中,传递给 onTouchEvent()
的 MotionEvent
提供每次交互的详细信息。你的应用可以使用 MotionEvent
提供的数据来确定是否发生了你关心的手势。
捕获活动或视图的触摸事件
要拦截 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; } });
注意不要创建返回 false
的侦听器,用于 ACTION_DOWN
事件。如果你这样做,侦听器不会针对后续的 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; } } }