检测常用的手势

编写:Andrwyw - 原文:http://developer.android.com/training/gestures/detector.html

当用户把用一根或多根手指放在触摸屏上,并且应用把这样的触摸方式解释为特定的手势时,“触摸手势”就发生了。相应地,检测手势也就有以下两个阶段:

  1. 收集触摸事件的相关数据。
  2. 分析这些数据,看它们是否符合app所支持的手势的标准。

Support Library 中的类

本节课程的示例程序使用了GestureDetectorCompatMotionEventCompat类。这些类都是在 Support Library 中定义的。如果有可能的情况话,我们应该使用 Support Library 中的类,来为运行着Android1.6及以上版本系统的设备提供兼容性功能。需要注意的一点是,MotionEventCompat并不是MotionEvent的替代品,而是提供了一些静态工具类函数。我们可以把MotionEvent对象作为参数传递给这些工具类函数,来获得与触摸事件相关的动作(action)。

收集数据

当用户把用一根或多根手指放在触摸屏上时,会触发 View 上用于接收触摸事件的 onTouchEvent() 回调函数。对于一系列连续的、最终会被识别为一种手势的触摸事件(位置、压力、大小、添加另一根手指等等),onTouchEvent()会被调用若干次。

当用户第一次触摸屏幕时,手势就开始了。其后系统会持续地追踪用户手指的位置,在用户手指全都离开屏幕时,手势结束。在整个交互期间,被分发给 onTouchEvent() 函数的 MotionEvent 对象,提供了每次交互的详细信息。我们的app可以使用 MotionEvent 提供的这些数据,来判断某种特定的手势是否发生了。

为Activity或View捕获触摸事件

为了捕获Activity或View中的触摸事件,我们可以重写onTouchEvent()回调函数。

接下来的代码段使用了getActionMasked()函数,来从 event 参数中抽取出用户执行的动作。它提供了一些原始的触摸数据,我们可以使用这些数据,来判断某个特定手势是否发生了。

  1. public class MainActivity extends Activity {
  2. ...
  3. // This example shows an Activity, but you would use the same approach if
  4. // you were subclassing a View.
  5. @Override
  6. public boolean onTouchEvent(MotionEvent event){
  7. int action = MotionEventCompat.getActionMasked(event);
  8. switch(action) {
  9. case (MotionEvent.ACTION_DOWN) :
  10. Log.d(DEBUG_TAG,"Action was DOWN");
  11. return true;
  12. case (MotionEvent.ACTION_MOVE) :
  13. Log.d(DEBUG_TAG,"Action was MOVE");
  14. return true;
  15. case (MotionEvent.ACTION_UP) :
  16. Log.d(DEBUG_TAG,"Action was UP");
  17. return true;
  18. case (MotionEvent.ACTION_CANCEL) :
  19. Log.d(DEBUG_TAG,"Action was CANCEL");
  20. return true;
  21. case (MotionEvent.ACTION_OUTSIDE) :
  22. Log.d(DEBUG_TAG,"Movement occurred outside bounds " +
  23. "of current screen element");
  24. return true;
  25. default :
  26. return super.onTouchEvent(event);
  27. }
  28. }

然后,我们可以对这些事件做些自己的处理,以判断某个手势是否出现了。这种是针对自定义手势,我们所需要进行的处理。然而,如果我们的app仅仅需要一些常见的手势,如双击,长按,快速滑动(fling)等,那么我们可以使用GestureDetector类来完成。 GestureDetector可以让我们简单地检测常见手势,并且无需自行处理单个触摸事件。相关内容将会在下面的检测手势中讨论。

捕获单个view的触摸事件

作为onTouchEvent()的一种替换方式,我们也可以使用 setOnTouchListener() 函数,来把 View.OnTouchListener 关联到任意的View上。这样可以在不继承已有的 View 的情况下,也能监听触摸事件。比如:

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

创建listener对象时,注意 ACTION_DOWN 事件返回 false 的情况。如果返回 false,会让listener对象接收不到后续的ACTION_MOVEACTION_UP等系列事件。这是因为ACTION_DOWN事件是所有触摸事件的开端。

如果我们正在写一个自定义View,我们也可以像上面描述的那样重写onTouchEvent()函数。

检测手势

Android提供了GestureDetector类来检测常用的手势。它所支持的手势包括onDown()onLongPress()onFling() 等。我们可以把GestureDetector和上面描述的onTouchEvent()函数结合在一起使用。

检测所有支持的手势

当我们实例化一个GestureDetectorCompat对象时,需要一个实现了GestureDetector.OnGestureListener接口的类作为参数。当某个特定的触摸事件发生时,GestureDetector.OnGestureListener就会通知用户。为了让我们的GestureDetector对象能到接收到触摸事件,我们需要重写 View 或 Activity 的 onTouchEvent() 函数,并且把所有捕获到的事件传递给 detector 实例。

接下来的代码段中,on<TouchEvent> 型的函数的返回值是 true,意味着我们已经处理完这个触摸事件了。如果返回 false,则会把事件沿view栈传递,直到触摸事件被成功地处理了。

运行下面的代码段,来了解当我们与触摸屏交互时,动作(action)是如何触发的,以及每个触摸事件MotionEvent中的内容。我们也会意识到,一个简单的交互会产生多少的数据。

  1. public class MainActivity extends Activity implements
  2. GestureDetector.OnGestureListener,
  3. GestureDetector.OnDoubleTapListener{
  4. private static final String DEBUG_TAG = "Gestures";
  5. private GestureDetectorCompat mDetector;
  6. // Called when the activity is first created.
  7. @Override
  8. public void onCreate(Bundle savedInstanceState) {
  9. super.onCreate(savedInstanceState);
  10. setContentView(R.layout.activity_main);
  11. // Instantiate the gesture detector with the
  12. // application context and an implementation of
  13. // GestureDetector.OnGestureListener
  14. mDetector = new GestureDetectorCompat(this,this);
  15. // Set the gesture detector as the double tap
  16. // listener.
  17. mDetector.setOnDoubleTapListener(this);
  18. }
  19. @Override
  20. public boolean onTouchEvent(MotionEvent event){
  21. this.mDetector.onTouchEvent(event);
  22. // Be sure to call the superclass implementation
  23. return super.onTouchEvent(event);
  24. }
  25. @Override
  26. public boolean onDown(MotionEvent event) {
  27. Log.d(DEBUG_TAG,"onDown: " + event.toString());
  28. return true;
  29. }
  30. @Override
  31. public boolean onFling(MotionEvent event1, MotionEvent event2,
  32. float velocityX, float velocityY) {
  33. Log.d(DEBUG_TAG, "onFling: " + event1.toString()+event2.toString());
  34. return true;
  35. }
  36. @Override
  37. public void onLongPress(MotionEvent event) {
  38. Log.d(DEBUG_TAG, "onLongPress: " + event.toString());
  39. }
  40. @Override
  41. public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
  42. float distanceY) {
  43. Log.d(DEBUG_TAG, "onScroll: " + e1.toString()+e2.toString());
  44. return true;
  45. }
  46. @Override
  47. public void onShowPress(MotionEvent event) {
  48. Log.d(DEBUG_TAG, "onShowPress: " + event.toString());
  49. }
  50. @Override
  51. public boolean onSingleTapUp(MotionEvent event) {
  52. Log.d(DEBUG_TAG, "onSingleTapUp: " + event.toString());
  53. return true;
  54. }
  55. @Override
  56. public boolean onDoubleTap(MotionEvent event) {
  57. Log.d(DEBUG_TAG, "onDoubleTap: " + event.toString());
  58. return true;
  59. }
  60. @Override
  61. public boolean onDoubleTapEvent(MotionEvent event) {
  62. Log.d(DEBUG_TAG, "onDoubleTapEvent: " + event.toString());
  63. return true;
  64. }
  65. @Override
  66. public boolean onSingleTapConfirmed(MotionEvent event) {
  67. Log.d(DEBUG_TAG, "onSingleTapConfirmed: " + event.toString());
  68. return true;
  69. }
  70. }

检测部分支持的手势

如果我们只想处理几种手势,那么可以选择继承 GestureDetector.SimpleOnGestureListener 类,而不是实现 GestureDetector.OnGestureListener 接口。

GestureDetector.SimpleOnGestureListener 类实现了所有的 on<TouchEvent> 型函数,其中,这些函数都返回 false。因此,我们可以仅仅重写我们需要的函数。比如,下面的代码段中,创建了一个继承自 GestureDetector.SimpleOnGestureListener 的类,并重写了 onFling() 和 onDown() 函数。

无论我们是否使用GestureDetector.OnGestureListener类,最好都实现 onDown() 函数并且返回 true。这是因为所有的手势都是由 onDown() 消息开始的。如果让 onDown() 函数返回 false,就像GestureDetector.SimpleOnGestureListener类中默认实现的那样,系统会假定我们想忽略剩余的手势,GestureDetector.OnGestureListener中的其他函数也就永远不会被调用。这可能会导致我们的app出现意想不到的问题。仅仅当我们真的想忽略全部手势时,我们才应该让 onDown() 函数返回 false

  1. public class MainActivity extends Activity {
  2. private GestureDetectorCompat mDetector;
  3. @Override
  4. public void onCreate(Bundle savedInstanceState) {
  5. super.onCreate(savedInstanceState);
  6. setContentView(R.layout.activity_main);
  7. mDetector = new GestureDetectorCompat(this, new MyGestureListener());
  8. }
  9. @Override
  10. public boolean onTouchEvent(MotionEvent event){
  11. this.mDetector.onTouchEvent(event);
  12. return super.onTouchEvent(event);
  13. }
  14. class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
  15. private static final String DEBUG_TAG = "Gestures";
  16. @Override
  17. public boolean onDown(MotionEvent event) {
  18. Log.d(DEBUG_TAG,"onDown: " + event.toString());
  19. return true;
  20. }
  21. @Override
  22. public boolean onFling(MotionEvent event1, MotionEvent event2,
  23. float velocityX, float velocityY) {
  24. Log.d(DEBUG_TAG, "onFling: " + event1.toString()+event2.toString());
  25. return true;
  26. }
  27. }
  28. }