Source code analysis of Gesture Detector and detailed usage

Gesture: Gesture
Detector: Detection, Identification
Gesture Detector: Gesture Recognition
This class is mainly used to handle finger movements on the screen, such as scroll,fling, down,press, etc. Looking at the document of this class, we find that there are three interfaces defined in it: OnGestureListener, OnDoubleTapListener, OnContextClickListener and a static internal class, SimpleOnGestureListener. This static internal class implements the three interfaces mentioned above.
The benefits of this static inner class are:
1. When we don't want to rewrite so many methods of an interface, we can use this static inner class. Because this static inner class has empty implementations of three interfaces, we don't need to implement all the methods of these three interfaces anymore. We just need to rewrite the methods we care about.
2. If we want to monitor multiple gestures, for example, we need to listen for both clicks and double-clicks on the screen.
The gestureDetector object sets up multiple listening objects. To simplify these steps, a SimpleOnGestureListener listening object can be set directly to the gestureDetector object.
In order to better use Gesture Detector, we need to carefully understand the three interface response gesture callback methods, first of all, look at the OnGestureListener interface in each method.

    public interface OnGestureListener {

        //This method triggers every time you touch the screen
        boolean onDown(MotionEvent e);

        //If the pressing time exceeds 180 ms, and after pressing, there is no loosening or sliding, this method will be executed.
	//Looking at the notes on the document, the user has executed the down motion event, and the user has not yet executed the move or raised his finger.
	//This event is usually used to give the user a visual feedback so that the user knows that their action has been recognized.
        void onShowPress(MotionEvent e);

	//A single tap will execute the method.
	//Trigger order:
        //Click on the very fast (no sliding) Touchup:
        //onDown->onSingleTapUp->onSingleTapConfirmed 
   	//Click on the slightly slower (no sliding) Touchup:
   	//onDown->onShowPress->onSingleTapUp->onSingleTapConfirmed
        boolean onSingleTapUp(MotionEvent e);

	//This method can be triggered by a finger moving on the screen or by a finger skimming across the screen (pressing, sliding quickly and loosening).
	//Trigger order:  
	//Slowly move your finger across the screen onDown - > onShowPress (possibly) - > multiple onScroll s
	//Mobile phones quickly scan onDown - > multiple onScrolls - > onFling on the screen

	// Parametric interpretation:
	// e1: First ACTION_DOWN Motion Event
	// e2: Last ACTION_MOVE MotionEvent
	// Velocity X: Moving speed on X axis, pixels per second
	// Velocity Y: Moving speed on the Y axis, pixels per second   
        boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);

	//If you press your finger long on the screen, it triggers this method.
	// Trigger order: onDown - > onShowPress - > onLongPress
        void onLongPress(MotionEvent e);

	//The user's finger flicks across the screen (press, slide quickly and release)
        boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
    }

    public interface OnDoubleTapListener {
	
	//This method executes when the finger clicks on the screen
	//Trigger order: onDown - > onShowPress (possibly) - > onSingleTapUp - > onSingleTapConfirmed
	//The difference between onSingleTapUp and onSingleConfirmed is that the first time the phone leaves the screen, whether it is double-clicked or clicked.
	//The onSingleUp method is triggered, and if the finger clicks on the screen, the onSingleConfirmed method is triggered. The gesture used to determine this time is to click
	//If the finger double-clicks the screen, the onSinlgeConfirmed method will not be triggered
        boolean onSingleTapConfirmed(MotionEvent e);
 
	//This method is performed when the finger double-clicks the screen.
	//The trigger order is: onDown - > onSingleTapUp - > onDoubleUp
        boolean onDoubleTap(MotionEvent e);

	//When a finger double-clicks on the screen, this method is executed if an event such as down,move,up occurs after the onDoubleUp method triggers.
	//Trigger order: onDown - > onSingleUp - > onDoubleUp - > onDoubleTapEvent - > onDown - > multiple onDoubleTapEvents
        boolean onDoubleTapEvent(MotionEvent e);
    }

    //This interface is used to monitor gestures of some external devices.
    public interface OnContextClickListener {
        boolean onContextClick(MotionEvent e);
    }

After understanding the three internal interfaces and one static internal class of the GestureDetector class, let's look at how the GestureDetector is constructed

 public GestureDetector(Context context, OnGestureListener listener) {
        this(context, listener, null);
    }

    public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
        if (handler != null) {
            mHandler = new GestureHandler(handler);
        } else {
            mHandler = new GestureHandler();
        }
        mListener = listener;
        if (listener instanceof OnDoubleTapListener) {
            setOnDoubleTapListener((OnDoubleTapListener) listener);
        }
        if (listener instanceof OnContextClickListener) {
            setContextClickListener((OnContextClickListener) listener);
        }
        init(context);
    }

    //This constructor is somewhat baffling. Although the unused parameter is to be passed in, it is not used. I don't know if it is a bug in google.
    public GestureDetector(Context context, OnGestureListener listener, Handler handler,
            boolean unused) {
        this(context, listener, handler);
    }

From the above three constructors, we can see that all of them are constructors with three parameters. Let's look at the concrete implementation of the constructor of three parameters:

    public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
        if (handler != null) {
            mHandler = new GestureHandler(handler);
        } else {
            mHandler = new GestureHandler();
        }
        mListener = listener;
        if (listener instanceof OnDoubleTapListener) {
            setOnDoubleTapListener((OnDoubleTapListener) listener);
        }
        if (listener instanceof OnContextClickListener) {
            setContextClickListener((OnContextClickListener) listener);
        }
        init(context);
    }

From the constructor, you can see that within the constructor, if handler is passed, a handler object is created as a parameter of the constructor of the class GestureHandler, and a hanler object is created using GestureHandler if the handler object is not passed in. Assign the incoming OnGestureListener to the mLister and call the init(context) method for some initialization operations. As you can see from the source code of GestureHandler, the internal class of GestureDetector, the only difference between the two ways of creating handlers is that they use different Looper s. Let's look at the class GestureHandler, which is the internal class of GestureDetector and inherits Hander, which is actually a Handler.

private class GestureHandler extends Handler {
        GestureHandler() {
            super();
        }

        GestureHandler(Handler handler) {
            super(handler.getLooper());
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case SHOW_PRESS:
                mListener.onShowPress(mCurrentDownEvent);
                break;
                
            case LONG_PRESS:
                dispatchLongPress();
                break;
                
            case TAP:
                // If the user's finger is still down, do not count it as a tap
                if (mDoubleTapListener != null) {
                    if (!mStillDown) {
                        mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
                    } else {
                        mDeferConfirmSingleTap = true;
                    }
                }
                break;

            default:
                throw new RuntimeException("Unknown message " + msg); //never
            }
        }
    }

If we create a GestureDetector object directly in the main thread using two parameters, the handler inside the GestureDetector uses the handler of the main thread. If you create a GestureDetector object in a subthread, if you create it using a two-parameter constructor, you will report an error
Java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
So, pay special attention when using Gesture Detector for sub-threads. Mainly pay attention to using handler in sub-threads. If you want to create a GestureDetector object in a sub-thread, there are three ways:

Way 1: When using this method, we should pay attention to onShowPress, onLongPress and onSingleTapConfirmed. These three methods are called back in handleMessage method. If the thread created by handler is in the sub-thread, then these three methods are called back in the sub-thread. Therefore, the operation of ui cannot be handled in these three methods. The code is as follows:

 new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                gestureDetector = new GestureDetector(GestureDetectorActivity2.this,gestureListener);
                gestureDetector.setOnDoubleTapListener(onDoubleTapListener);
                Looper.loop();
            }
        }).start();

Mode 2: (create handler with looper of main thread, and then create GestureDetector object with constructor of three parameters of GestureDetector)

 new Thread(new Runnable() {
            @Override
            public void run() {
                Handler handler = new Handler(Looper.getMainLooper());
                gestureDetector = new GestureDetector(GestureDetectorActivity2.this,gestureListener,handler);
                gestureDetector.setOnDoubleTapListener(onDoubleTapListener);
            }
        }).start();

Mode 3: Create handler objects directly in the main thread, and use this handler to create GestureDetector objects.

        final Handler handler = new Handler();
        new Thread(new Runnable() {
            @Override
            public void run() {
                gestureDetector = new GestureDetector(GestureDetectorActivity2.this,gestureListener,handler);
                gestureDetector.setOnDoubleTapListener(onDoubleTapListener);
            }
        }).start();

Here are two ways to create Gesture Detector directly in the main thread:
Mode 1:

        GestureDetector gestureDetector = new GestureDetector(GestureDetectorActivity2.this,gestureListener);
        gestureDetector.setOnDoubleTapListener(onDoubleTapListener);

Mode 2: (SimpleOnGestureListener method, on-demand implementation, if not, can not be implemented)

GestureDetector  gestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener(){
            @Override
            public boolean onDown(MotionEvent e) {
                return super.onDown(e);
            }

            @Override
            public void onShowPress(MotionEvent e) {
                super.onShowPress(e);
            }

            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                return super.onSingleTapUp(e);
            }

            @Override
            public boolean onSingleTapConfirmed(MotionEvent e) {
                return super.onSingleTapConfirmed(e);
            }

            @Override
            public boolean onDoubleTap(MotionEvent e) {
                return super.onDoubleTap(e);
            }

            @Override
            public boolean onDoubleTapEvent(MotionEvent e) {
                return super.onDoubleTapEvent(e);
            }

            @Override
            public void onLongPress(MotionEvent e) {
                super.onLongPress(e);
            }

            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                return super.onScroll(e1, e2, distanceX, distanceY);
            }

            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
                return super.onFling(e1, e2, velocityX, velocityY);
            }
        });

The principle of Gesture Detector is that by intercepting each Motion Event in onTouch Event, a set of rules defined by oneself, such as what is click and what is double-click, can be divided more carefully, and the rules defined by oneself (such as click events) can be dispatched through one's own internal handler. Message, then handler Message
In this method, the corresponding callback method in GestureListener interface is called to implement the monitoring of onShowPress, onLongPress and onSingleTapConfirmed operations. In Gesture Detector's onTouchEvent method, callbacks for onDown, onSingleTapUp, onDoubleTap, onDoubleTapEvent, onScroll, onFling are made.

The use steps are as follows:
1. Create a GestureDetector.SimpleOnGestureListener simpleLinener = new GestureDetector.SimpleOnGestureListener() {// Implementing the method of alignment that you care about} object
2. Create GestureDetector objects
GestureDetector gestureDetector = new GestureDetector(simpleLinener);
3. Processing MotionEvent actions in onTouchListener of the view to be monitored
tv.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
gestureDetector.onTouchEvent(event);
return false;
}
});

Tags: Mobile Google Java

Posted on Fri, 06 Sep 2019 03:02:41 -0700 by u0206787@nus.edu.sg