The introduction

During Android development, we often need to monitor gestures such as click, double click, long press, swipe, zoom, etc. At this time, the concept of gesture monitoring is introduced. The so-called gesture monitoring, to put it plainly, is the use of GestureDetector and the learning of key points to pay attention to. Note: Due to the unique complexity of pinch gestures, I plan to summarize them separately later.

Like many other blogs on the web that monitor gestures on Android, this article will take a step-by-step look at Android gesture monitoring using the double click event as a starting point. What you need to know is the same thing: After reading this article, I still can’t GestureDetector gesture detection.


Double-click on the 666

For a newcomer to Android, what do we think if you need to implement a double click feature?

May Be

  1. First, let’s override the onTouchEvent method
  2. When clicked for the first time, we determine whether it is the control we want to listen to
  3. If so, start a new thread and start the countdown (e.g. 1s).
  4. If the click event is called again during this countdown
  5. A double – click event occurs ⌚️

But it’s so complicated, you have to control the time, you have to judge the controls, and so on. So, how do we solve it? Use of gesture listening


GestureDetector use

My understanding is that GestureDetector is an object in Android specially used for gesture monitoring. In its listener, we can monitor various gestures in the callback method of various events by passing in the MotionEvents object. Here’s an example: GestureDetector’s OnGestureListener is a callback method, which means that after receiving the passed MotionEvents object and processing it, we can listen for clicks by overwriting various methods in it (click events, double click events, etc.). Events such as double – clicks, slides, and so on are then handled directly within those methods.

Method of use

  1. First, create a SimpleOnGestureListener callback method object and override the individual methods in it
  2. Based on this Listener object, instantiate the GestureDetector object
  3. Override the setOnTouchListener method on the target control and call the Detector object’s onTouchEvent method in it

Easy to understand, in a minute

    @Override
    protected void onResume(a) {
        button.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                returndetector.onTouchEvent(event); }});super.onResume();
    }

    private void iniGestureListener(a){
        GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){
            @Override
            public boolean onDoubleTap(MotionEvent e) {
                MyToast.makeToast(GestureDetectorActivity.this."double click up!");
                return super.onDoubleTap(e);
            }

        detector = new GestureDetector(GestureDetectorActivity.this, listener);
    }
Copy the code

The results


Which registered

If you’re curious, try creating the Detector object in a thread (like the one below). It is possible for the program to crash at runtime. Why?

        new Thread(){
            @Override
            public void run(a) {
                super.run();
                detector = new GestureDetector(GestureDetectorActivity.this, listener);
            }
        }.start();
Copy the code

When the GestureDetector is instantiated, a Handler is automatically created internally to process the data, so if you create the GestureDetector in the main thread, The Handler created inside the GestureDetector will automatically get the main thread’s Looper. Hence: If you create a GestureDetector in a child thread that has not created a Looper, you need to pass it a Handler with a Looper, otherwise the creation will fail because it will not get the ==Looper==.

To solve

Now that the problem has arisen, how can we solve it? The active Looper== Looper== is missing. The detector== constructor has two common methods. The first is the one we use in the main thread, and the other is the one we are using now. In the child thread, we can pass in the Looper constructor:

public GestureDetector(Context context, OnGestureListener listener)
GestureDetector(Context context, GestureDetector.OnGestureListener listener, Handler handler)

Scheme one passes in a Hander that holds a Looper object

        new Thread(){
            @Override
            public void run(a) {
                super.run();
                detector = new GestureDetector(GestureDetectorActivity.this, listener, new Handler(Looper.getMainLooper()));
            }
        }.start();
Copy the code

Scheme two is the same as method, but take Hander out and create it separately

        new Thread(){
            @Override
            public void run(a) {
                super.run();
                Handler handler = new Handler(Looper.getMainLooper());
                detector = new GestureDetector(GestureDetectorActivity.this, listener, handler);
            }
        }.start();
Copy the code

Scheme 3 creates the Hander in the main thread so that the main thread’s Looper is not passed in when the Hander is created

        final Handler handler = new Handler();
        new Thread(){
            @Override
            public void run(a) {
                super.run();
                detector = new GestureDetector(GestureDetectorActivity.this, listener, handler);
            }
        }.start();
Copy the code

Plan 4 is the same as the above methods, except that the Lopper is prepared in advance in the subline scale, so that the line scale is the same as the main thread

        new Thread(){
            @Override
            public void run(a) {
                super.run();
                Looper.prepare();
                detector = new GestureDetector(GestureDetectorActivity.this, listener);
            }
        }.start();
Copy the code

Key points: gesture monitoring

We’ve already covered onDoubleTapEvent with double click effects, so what else are the awesome callbacks of GestureDetecotr?

  1. OnDoubleTapListener: double click event. In addition to the onDoubleTapEvent callback, there are also SingleTapConfirmed and DoubleTap callback methods
  2. OnGestureListener: A listener that has many gestures: Down, Fling, LongPress, Scroll, ShowPress and SingleTapUp
  3. SimpleOnGestureListener: Empty implementation of the above interface, used more frequently

OnDoubleTapListener

Let’s talk about OnDoubleTapListener, and you might say, “Well, I’ve already talked about double clicking on events, but it’s a waste of time.” Without further ado, let me elaborate on this kind of method:

Click callback SingleTapConfirmed

One might wonder why SingleTapConfirmed is used instead of onClickListener for a click callback.

First of all, these two methods are in conflict, and this involves the event distribution mechanism, which I will summarize for you later, but I won’t go into details here.

Second, with the onClickListener mechanism, it is not difficult to find that if we use onClickListener, when we double-click, we will also call the click event, that is, click twice, which is obviously not in line with our intention. So how do you call it? Very easy!

        final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){
            @Override
            public boolean onSingleTapConfirmed(MotionEvent e) {
                MyToast.makeToast(GestureDetectorActivity.this."single click!");
                return super.onSingleTapConfirmed(e); }... };Copy the code

DoubleTap and onDoubleTapEvent

I’m going to put these two methods together, one is that they both fall into the category of double-click, and the other is that they have very high similarities and subtle but important differences.

You can try printing Down moves and Up clicks in onTouchEvent and DoubleTap, and you’ll see that for DoubleTap, it’s the callback that happens when you press yes on the second click, In the case of onDoubleTapEvent, the callback occurs when the finger lifts away from the screen after the second click. This is the most important difference between them.

    final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){

        @Override
        public boolean onDoubleTap(MotionEvent e) {
            MyToast.makeToast(GestureDetectorActivity.this."double click down!");
            return super.onDoubleTap(e);
        }

        @Override
        public boolean onDoubleTapEvent(MotionEvent e) {
            switch (e.getActionMasked()){
                case MotionEvent.ACTION_UP:
                    MyToast.makeToast(GestureDetectorActivity.this."double click up!");
                    break;
            }
            return super.onDoubleTapEvent(e); }};Copy the code

So, with these two approaches, we can meet both needs more purposefully. Now that we’re done with the double click event, let’s move on to the OnGestureListener


OnGestureListener

This can be said to be the core part of the whole gesture monitoring, the previous are introduced, now is the topic, here I mainly introduce to you gesture:

  1. Press (Down)
  2. A throw (Fling)
  3. Long press (LongPress)
  4. Rolling (Scroll)
  5. Touch Feedback (ShowPress)
  6. Click Lift (SingleTapUp)

onDown

The onDown event is well understood, it is executed when a View is pressed. As such, in order to perform onDown, the View must first be clickable, that is, onClickable is true.

    private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){

        @Override
        public boolean onDown(MotionEvent e) {
            MyToast.makeToast(GestureDetectorActivity.this."onDown");
            // Subsequent events
            return super.onDown(e); }};Copy the code

onFling

I personally feel that onFling is one of the most commonly used methods and, as its name translates to “drag, drag, and throw.” We use RecyclerView and ListView for example. We use onFling to test this gesture by quickly scrolling up and then stopping.

    private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            mSpeedX = velocityX;
            mSpeedY = velocityY;
            handler.postDelayed(runnable, 30);
            return super.onFling(e1, e2, velocityX, velocityY); }};Copy the code

As you can see from the code, this method takes four parameters

parameter meaning
e1 Event when a finger is pressed.
e2 Event when the finger is lifted.
velocityX The speed of motion on the X-axis in pixels per second.
velocityY Velocity in pixels per second on the Y axis.

With the first two MotionEvent parameters, we can get the location of the click, etc., and with the last two float parameters, we can get the speed of the finger slide.

Specific use is actually quite a lot, for example, we can imagine the billiards game, after the club hit, there is such an initial speed diminishing effect.

onLongPress

OnLongPress is a simple callback to long press events, such as long press copy, long press popover, etc. It is not only widely used, but also very simple to use, there is no nagging here

    private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){

        @Override
        public void onLongPress(MotionEvent e) {
            MyToast.makeToast(GestureDetectorActivity.this."onLongPress");
            // Follow-up work
            super.onLongPress(e); }};Copy the code

onScroll

OnScroll is very similar to onFling, except that onScroll parameters are the speed of the slide, while onScroll parameters are the distance.

parameter meaning
e1 MotionEvent when a finger is pressed
e2 The MotionEvent when the finger is lifted
distanceX The distance traveled along the X axis
distanceY The distance traveled along the Y-axis
    private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            MyToast.makeToast(GestureDetectorActivity.this."onScroll X = " + 
                    distanceX + " Y = " + distanceY);
            return super.onScroll(e1, e2, distanceX, distanceY); }};Copy the code

onShowPress

This method is not very useful because it is called when the View is clicked (pressed). Its function is to give visual feedback to the user and let the user know that the control is clicked. This effect can be achieved by using Material Design ripple. Or you can just drawable a background.

If anything, it is a delayed callback with a latency of 180 ms. That is, if the user’s finger is immediately lifted or the event is immediately blocked within 180 ms, the message will be removed and the callback will not be triggered.

    private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){
        @Override
        public void onShowPress(MotionEvent e) {
            MyToast.makeToast(GestureDetectorActivity.this."onShowPress");// >150ms
            super.onShowPress(e); }};Copy the code

onSingleTapUp

There’s a lot of analysis online for onSingleTapUp, but I think it’s too complicated. It’s actually very simple. Here’s an example to get the idea:

We talked about double click events earlier, so onSingleTapUp is a callback on the first click of a double click event. That is, when you click a control (double click the first time), the callback will be called immediately, and then quickly click the second time (double click the second time of the event), it will not be called.

type Trigger a number of Abstract
onSingleTapUp 1 Triggered on the first lift of a double – click
onSingleTapConfirmed 0 No trigger when a double click occurs.
onClick 2 Fires twice on a double click event.

The difference between onSingleTapConfirmed and onSingleTapConfirmed is that onSingleTapConfirmed will call back twice on a double click, whereas onSingleTapUp will only call back on the first double click.

    private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){
        @Override
        public boolean onSingleTapUp(MotionEvent e) {// Double-click the first time to lift the trigger, the second time does not trigger
            Log.d("onSingleTapUp"."onSingleTapUp");// >150ms
            return super.onSingleTapUp(e); }};Copy the code

SimpleOnGestureListener

SimpleOnGestureListener contains an empty implementation of all of these methods, and I mention it again at the end of this article mainly for its convenience.

Let’s take listening on OnDoubleTapListener as an example. If you want to use the OnDoubleTapListener interface, you need to set this up:

GestureDetector detector = new GestureDetector(this.new GestureDetector
        .SimpleOnGestureListener());
detector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
    @Override public boolean onSingleTapConfirmed(MotionEvent e) {
        Toast.makeText(MainActivity.this."onSingleTapConfirmed", Toast.LENGTH_SHORT).show();
        return false;
    }

    @Override public boolean onDoubleTap(MotionEvent e) {
        Toast.makeText(MainActivity.this."onDoubleTap", Toast.LENGTH_SHORT).show();
        return false;
    }

    @Override public boolean onDoubleTapEvent(MotionEvent e) {
        Toast.makeText(MainActivity.this."onDoubleTapEvent",Toast.LENGTH_SHORT).show();
        return false; }});Copy the code

It is not difficult to find a problem. Since a SimpleOnGestureListener has been instantiated during GestureDetector instantiation, using OnGestureListener at a distance will result in several empty implementations. Obviously wasteful, so in general, just use SimpleOnGestureListener.

The last

SimpleOnGestureListener (SimpleOnGestureListener, SimpleOnGestureListener)

    private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){

        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            MyToast.makeToast(GestureDetectorActivity.this."single click!");
            return super.onSingleTapConfirmed(e);
        }

        @Override
        public boolean onDoubleTap(MotionEvent e) {
            MyToast.makeToast(GestureDetectorActivity.this."double click down!");
            return super.onDoubleTap(e);
        }

        @Override
        public boolean onDoubleTapEvent(MotionEvent e) {
            switch (e.getActionMasked()){
                case MotionEvent.ACTION_UP:
                    MyToast.makeToast(GestureDetectorActivity.this."double click up!");
                    break;
            }
            return super.onDoubleTapEvent(e);
        }

        @Override
        public boolean onDown(MotionEvent e) {
            MyToast.makeToast(GestureDetectorActivity.this."onDown");
            return super.onDown(e);
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            mSpeedX = velocityX;
            mSpeedY = velocityY;
            handler.postDelayed(runnable, 30);
            return super.onFling(e1, e2, velocityX, velocityY);
        }

        @Override
        public void onShowPress(MotionEvent e) {
            MyToast.makeToast(GestureDetectorActivity.this."onShowPress");// >150ms
            super.onShowPress(e);
        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {// Double-click the first time to lift the trigger, the second time does not trigger
            Log.d("onSingleTapUp"."onSingleTapUp");// >150ms
            return super.onSingleTapUp(e);
        }

        @Override
        public void onLongPress(MotionEvent e) {
            MyToast.makeToast(GestureDetectorActivity.this."onLongPress");
            // Follow-up work
            super.onLongPress(e);
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            MyToast.makeToast(GestureDetectorActivity.this."onScroll X = " +
                    distanceX + " Y = " + distanceY);
            return super.onScroll(e1, e2, distanceX, distanceY); }};Copy the code

This blog is a summary of my learning process, so it is inevitable that there are omissions, I hope you can point out in the comments section, thank you very much.

At the same time, if you have any questions, you can also leave a comment in the comments section, this washboard kneel or not kneel, you decide! 🙏