Handling longpress/longclick in MapActivity

You might be interested to know that I have written a new post on this same topic, with a solution that is cleaner and works better to boot. You can find it here.

While working on my MapActivity based Android app, I wanted to be able to show a context menu when the user longpressed any point on the map (not necessarily a marker in an overlay) and perform some action related to that point. I wrongly assumed that there would be some simple method to override for this, like onLongPress(). Therefore I set out to find another solution, and here it is. It’s not perfect, but it works really well in my app, and I hope others might find this useful as well.

Stuff that didn’t work

  • Simply overriding onCreateContextMenu() in the MapActivity. This would not yield a context menu upon longpress on the map.
  • setOnLongClickListener() in the MapView to catch a longpress/longclick, and then manually call MapView.showContextMenu() to display the menu. The listener never caught an event.

I also briefly looked at the mapview-overlay-manager library, which might work for you.  It will, among many other things, give you an onLongPress() event. I figured this to be overkill for solving my problem.

My solution

I ended up overriding MapActivity.dispatchTouchEvent(MotionEvent event), which processes all touch events to the activity. When overriding this method, we need to take care of the following elements to handle longpresses:

  1. If the finger has just touched the screen, we start checking how long it is held. We need to do this in a separate thread, or else the UI will lock up. If the checks in the following point do not kick in within a given threshold, we have a longpress and call the desired code, In my case this was to show the context menu.
  2. If some motion event that can not be part of a longpress has been performed, cancel the check in pt.2. These events are:
    • Finger has been removed from screen
    • Finger has moved more than a given threshold since the
      previous event

    Here is the complete MapActivity, handling all this:

    The code

    Although I admit not being a very experienced Java programmer, this is not my actual implementation, but a simplified example to show which elements needs to be in place for this to work. Horizontal scrollbar at the bottom.

    import android.os.Bundle;
    import android.os.Looper;
    import android.view.MotionEvent;
    
    import com.google.android.maps.MapActivity;
    
    public class Map extends MapActivity {
    	private boolean isPotentialLongPress;
    
    	@Override
    	public void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.main);
    	}
    
    	@Override
    	public boolean dispatchTouchEvent(MotionEvent event) {
    		handleLongPress(event);
    		return super.dispatchTouchEvent(event);
    	}
    
    	private void handleLongPress(MotionEvent event) {
    		if (event.getAction() == MotionEvent.ACTION_DOWN) {
    			// A new touch has been detected
    
    			new Thread(new Runnable() {
    				public void run() {
    					Looper.prepare();
    					if (isLongPressDetected()) {
    						// We have a longpress! Perform your action here
    					}
    				}
    			}).start();
    		} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
    			/*
    			 * Only MotionEvent.ACTION_MOVE could potentially be regarded as
    			 * part of a longpress, as this event is trigged by the finger
    			 * moving slightly on the device screen. Any other events causes us
    			 * to cancel this events status as a potential longpress.
    			 */
    			if (event.getHistorySize() < 1)
    				return; // First call, no history
    
    			// Get difference in position since previous move event
    			float diffX = event.getX()
    					- event.getHistoricalX(event.getHistorySize() - 1);
    			float diffY = event.getY()
    					- event.getHistoricalY(event.getHistorySize() - 1);
    
    			/* If position has moved substatially, this is not a long press but
    			   probably a drag action */
    			if (Math.abs(diffX) > 0.5f || Math.abs(diffY) > 0.5f) {
    				isPotentialLongPress = false;
    			}
    		} else {
    			// This motion is something else, and thus not part of a longpress
    			isPotentialLongPress = false;
    		}
    	}
    
    	/**
    	 * Loops for an amount of time while checking if the state of the
    	 * isPotentialLongPress variable has changed. If it has, this is regarded as
    	 * if the longpress has been canceled. Else it is regarded as a longpress.
    	 */
    	public boolean isLongPressDetected() {
    		isPotentialLongPress = true;
    		try {
    			for (int i = 0; i < (50); i++) {
    				Thread.sleep(10);
    				if (!isPotentialLongPress) {
    					return false;
    				}
    			}
    			return true;
    		} catch (InterruptedException e) {
    			return false;
    		} finally {
    			isPotentialLongPress = false;
    		}
    	}
    
    	@Override
    	protected boolean isRouteDisplayed() {
    		return false;
    	}
    
    }
    

    If you have a better way of doing this, I’d love to hear from you.

7 thoughts on “Handling longpress/longclick in MapActivity

  1. Thanks for your solution.We don’t need thread.

    if (event.getAction() == MotionEvent.ACTION_DOWN) {
    eventTimee = event.getEventTime();
    } else if (event.getAction() == MotionEvent.ACTION_UP) {
    long time;
    time = event.getEventTime() – eventTimee;

    if (time > 500) {
    // Perform your action here
    } else
    eventTimee = 0;
    }

    that’s all.

  2. Hi Burak,

    Thanks for commenting. I see two possible issues with your solution:

    1) It would not trigger an action until you actually lift your finger from the screen. I feel this is less intuitive than it can be.
    2) It would trigger even after just scrolling around the map, as long as the given time threshold is exceeded.

    Am I missing something obvious here?

    -Roger

  3. Another good possibility is to create a class that extends Overlay and implements
    GestureDetector.OnGestureListener. In this class, handle the events like you want.

    You can than, in your mapActivity, get the overlays array and add the class you just created.

    I think is a better solution for this problem and you get a better code modulation.

Leave a Reply

Your email address will not be published. Required fields are marked *