Friday, 22 August 2014

Using GestureDetector in Android ImageView

I've found quite a few discussions on the Internet with regards to using GestureDetector for your ImageView; some of them are more useful than the others. The bottom line is: there are two ways of doing this:

  • Either subclassing ImageView and using newly created view in your activity XML file
  • Or simply delegating all events from ImageView to the gesture detector in your activity.
In fact, second way is extremely simple, so I want to share with you an example of how this can be done.


public class ShowImageActivity extends Activity {

  private static final String TAG = ShowImageActivity.class.getSimpleName();

  class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
    @Override
    public void onLongPress(MotionEvent e) {
      Log.d(TAG, "onLongPress " + e.toString());
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
      Log.d(TAG, "onSingleTapConfirmed " + e.toString());
      return true;
    }

    // You really need all these boilerplate methods for GestureDetector to work

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
      return true;
    }
    @Override
    public boolean onDown(MotionEvent e) {
      return true;
    }
  }
  
  // Creating a GestureDetector with our listener, all events will be
  // delivered via the callbacks of MyGestureListener class.
  GestureDetector detector = new GestureDetector(new MyGestureListener());
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_show_image);

    Log.d(TAG, "Creating ShowImageActivity");

    this.imageView = (ImageView)findViewById(R.id.imageView);
    
    // Some way of obtaining a bitmap for the ImageView, largely
    // irrelevant for this example.

    Uri uri = (Uri) getIntent().getParcelableExtra("bitmapUri");
    Bitmap bitmap = null;
    try {
      bitmap = getBitmapFromUri(uri);
      imageView.setImageBitmap(bitmap);

      // Important bit: delegating on onTouch events processing to our
      // newly defined Gesture Detector. 
      imageView.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
          return ShowImageActivity.this.detector.onTouchEvent(motionEvent);
        }
      });
    } catch (IOException e) {
      Log.e(TAG, "Error loading bitmap", e);
    }
  }
}


Let's discuss what are we doing here. To start with, in our activity class we define our own class MyGestureListener, which extends SimpleOnGestureListener. In fact, you can get away with just extending OnGestureListener class, but this way theoretically you should only implement methods you really need to (not true, but there are still less methods to implement).

There are two methods implemented on the top - onSingleTapConfirmed and onLongPress. Let's assume these two are the methods we are really interested in. In addition to those, you will have to override onSingleTapUp and onDown - for the reasons I don't understand, SimpleOnGestureListener implements them in a way that they return false, i.e. all further processing stops and you will only be receiving onLongPress callbacks.

Once you have this class, you can create your own GestureDetector by just specifying the listener which will be processing the events. Note, that it is probably a good idea to run this listener on android.os.Looper thread rather than on UI thread, but for the sake of example this will do.

Finally, after we find and populate our ImageView with the bitmap (the way we do that is irrelevant for the sake of this example, so the method getBitmapFromUri is not included), we add an OnTouch listener to this image view and delegate every single event to our gesture detector. This is really it, all events will be delivered via MyGestureListener callbacks, and if you feel that you need to capture more events (such as double tap) you will only have to override a corresponding method of the SimpleOnGestureListener.

(you can use this GitHub gist to get you going)