Gestures are those subtle motions to trigger interactions between the touch screen and the user. It lasts for the time between the first touch on the screen to the point when the last finger leaves the surface. We’re all familiar with it as it’s the most common way to communicate with apps on our mobiles, tablets, etc. these days. Some examples are scrolling in an app by swiping vertically/horizontally, pinch to zoom, long press to select and so on.
Android provides us with a class called GestureDetector using which we can detect common gestures like tapping down and up, swiping vertically and horizontally (fling), long and short press, double taps, etc. and attach listeners to them. Let’s see how that’s done.
What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.
Implementing Listeners
We must implement the listeners to start detecting the gesture events. There are 2 ways to do that:
- Make our Activity class implement
GestureDetector.OnDoubleTapListener
(for double tap gesture detection) andGestureDetector.OnGestureListener
interfaces and implement all theabstract
methods. - Write a custom class as a nested class of our Activity or some other external class that extends the aforementioned interfaces or extends the
GestureDetector.SimpleOnGestureListener
class. The beauty ofSimpleOnGestureListener
nested class is that it implements all the methods from those 2 interfaces but does nothing. So if we want to deal with and process only a few (subset) gestures then we can take this approach.
I’ll demonstrate how to implement the 2 interfaces using the second approach:
class CustomGestureDetector implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener { @Override public boolean onSingleTapConfirmed(MotionEvent e) { mGestureText.setText("onSingleTapConfirmed"); return true; } @Override public boolean onDoubleTap(MotionEvent e) { mGestureText.setText("onDoubleTap"); return true; } @Override public boolean onDoubleTapEvent(MotionEvent e) { mGestureText.setText("onDoubleTapEvent"); return true; } @Override public boolean onDown(MotionEvent e) { mGestureText.setText("onDown"); return true; } @Override public void onShowPress(MotionEvent e) { mGestureText.setText("onShowPress"); } @Override public boolean onSingleTapUp(MotionEvent e) { mGestureText.setText("onSingleTapUp"); return true; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { mGestureText.setText("onScroll"); return true; } @Override public void onLongPress(MotionEvent e) { mGestureText.setText("onLongPress"); } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { mGestureText.setText("onFling"); return true; } }
Although must of these methods are self-explanatory, in a state of confusion refer to the documentation of OnDoubleTapListener
and OnGestureListener
.
Creating the GestureDetector
Now, we have to create a GestureDetector
object and attach the listener objects to it which will intercept the gesture events. We’ll do that in the onCreate()
method of our Activity
class:
private TextView mGestureText; private GestureDetector mGestureDetector; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_gesture); mGestureText = (TextView) findViewById(R.id.gestureStatus); // Create an object of our Custom Gesture Detector Class CustomGestureDetector customGestureDetector = new CustomGestureDetector(); // Create a GestureDetector mGestureDetector = new GestureDetector(this, customGestureDetector); // Attach listeners that'll be called for double-tap and related gestures mGestureDetector.setOnDoubleTapListener(customGestureDetector); }
Pretty straightforward piece of code. One thing to notice is that, since we want to detect double-taps and related gestures, we call the setOnDoubleTapListener()
on our GestureDetector
object.
Implementing onTouchEvent()
Our gesture detectors won’t fire yet. This is because we arn’t intercepting the touch events and re-routing them to our gesture detectors. In order to do that we’ll have to override our Activity’s onTouchEvent()
method and do the re-routing there like this:
@Override public boolean onTouchEvent(MotionEvent event) { mGestureDetector.onTouchEvent(event); return super.onTouchEvent(event); }
Wow, that was really easy! Now you should just go and start testing the code out by running the app on your physical device and making various gestures on the screen.
Note: If you want to capture the touch events on a particular view rather than the entire Activity, then we’ll need to attach a View.OnTouchListener
object to the View object using setOnTouchListener
from whose onTouch()
method the re-routing to the gesture detectors will need to be done:
view.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, final MotionEvent event) { mGestureDetector.onTouchEvent(event); return true; } });
There’s another way to do the same thing which is to subclass a View class and override it’s onTouchEvent()
method to do the delegation but that’s a little complicated and messy.
Bonus: Detecting Swipe/Fling Direction
Detecting the fling directions like up to down, down to up, left to right and right to left can be a requirement which is fairly easy to implement inside the onFling()
method:
@Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { mGestureText.setText("onFling " + e1.getX() + " - " + e2.getX()); if (e1.getX() < e2.getX()) { Log.d(TAG, "Left to Right swipe performed"); } if (e1.getX() > e2.getX()) { Log.d(TAG, "Right to Left swipe performed"); } if (e1.getY() < e2.getY()) { Log.d(TAG, "Up to Down swipe performed"); } if (e1.getY() > e2.getY()) { Log.d(TAG, "Down to Up swipe performed"); } return true; }
e1
MotionEvent object contains data regarding the first down motion event that started the fling, i.e., the first down interaction with the touch screen by the finger (pointer), whereas, the e2
object contains data regarding the move motion event (end of gesture) that triggered onFling()
. getX/Y()
is used to get the X and Y co-ordinates.
Conclusion
The GestureDetector
class provided to us makes it really easy to detect most of the common gestures from basic physical contact with the screen like tapping to swiping and scrolling. Once detected we can build our app functionality on their invocation. Later we’ll see how to implement custom gestures like pinch to zoom.
Thanks for your tutorial. I understood this subject GestureDetector.
Little improving:
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (Math.abs(velocityX) > Math.abs(velocityY)) {
if (e1.getX() e2.getX()) {
Log.d(“CubeGLSurfaceView”, “Right to Left swipe performed”);
}
} else {
if (e1.getY() e2.getY()) {
Log.d(“CubeGLSurfaceView”, “Down to Up swipe performed”);
}
}
return true;
}