检测常用手势

“轻触手势”是指用户将一个或多个手指放在触摸屏上,并且您的应用会将这种轻触模式解读为特定手势。手势检测可相应地划分为两个阶段:

  1. 收集轻触事件的相关数据。
  2. 解读数据以确定其是否符合您的应用支持的任何手势的条件。

请参阅以下相关资源:

支持库类

这节课中的示例使用了 GestureDetectorCompat 类和 MotionEventCompat 类。这些类均包含在支持库中。您应尽量使用支持库类,以便与搭载 Android 1.6 及更高版本的设备兼容。请注意,MotionEventCompat 并非替代 MotionEvent 类。相反,它可以提供静态实用程序方法,您可以向这些方法传递 MotionEvent 对象,以便接收与相应事件相关的预期操作。

收集数据

当用户将一个或多个手指放在屏幕上时,便会在收到轻触事件的视图中触发回调 onTouchEvent()。对于最终被识别为手势的每个轻触事件(位置、压力、大小、添加另一个手指等)序列,onTouchEvent() 都会多次被触发。

手势会在用户首次轻触屏幕时开始,在系统跟踪用户手指的位置时继续,并在捕获到用户手指离开屏幕的最终事件时结束。在此互动过程中,传递给 onTouchEvent()MotionEvent 会提供每次互动的详细信息。您的应用可以使用 MotionEvent 提供的数据确定是否发生了它所关注的手势。

捕获 Activity 或视图的轻触事件

要拦截 Activity 或视图中的轻触事件,请替换 onTouchEvent() 回调。

以下代码段使用 getActionMasked()event 参数中提取用户执行的操作。这可为您提供所需的原始数据,以供您确定是否发生了您所关注的手势:

Kotlin

    class MainActivity : Activity() {
        ...
        // This example shows an Activity, but you would use the same approach if
        // you were subclassing a View.
        override fun onTouchEvent(event: MotionEvent): Boolean {

            val action: Int = MotionEventCompat.getActionMasked(event)

            return when (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, but you would use the same approach if
    // you were subclassing a View.
    @Override
    public boolean onTouchEvent(MotionEvent event){

        int action = MotionEventCompat.getActionMasked(event);

        switch(action) {
            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);
        }
    }
    

然后,您可以自行处理这些事件,以确定手势是否已发生。您需要对自定义手势执行此类处理。不过,如果您的应用使用的是常用手势(例如点按两次、长按、滑动等),则可以利用 GestureDetector 类。借助 GestureDetector,您可以轻松地检测常用手势,而无需亲自处理单个轻触事件。下面的检测手势部分详细介绍了这一内容。

捕获单个视图的轻触事件

除了 onTouchEvent() 之外,您还可以将 View.OnTouchListener 对象附加到任何 View 对象,为此,您需要使用 setOnTouchListener() 方法。这样即可监听轻触事件,而无需创建现有 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 对象能够接收事件,您可以替换视图或 Activity 的 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,最佳做法是实现一个返回 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){
            this.mDetector.onTouchEvent(event);
            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;
            }
        }
    }