Logo Icon Logo
A Crowd-sourced Cookbook on Writing Great Android® Apps
GitHub logo Twitter logo OReilly Book Cover Art

Handling longpress in a map

Author: Roger Kind Kristiansen
Published? false -- FormatLanguage: W

Problem:

For some map applications you might want to let the user trigger an action related to an arbitrary point on the map, for example through a context menu. Enabling the user to do a longpress on the map is among the more intuitive ways to expose this kind of functionality, but support for this is not built in to Android. Let me show you how you can add it yourself.

Solution:

We create a subclass of MapView, in which we define an OnLongpressListener interface as well as overriding MapView.onTouchEvent() to insert our longpress detection logic. onTouchEvent() is triggered every time the user touches, moves or releases a finger on the map, which makes this the perfect place for our purpose.

After modifying our map layout file and using this map as the content of a MapActivity, we can finally create an OnLongPressListener object, add it to our MapView subclass object, and enjoy some longpress action.

Discussion:

Warning The Google Maps V1 API is deprecated; new code should use the V2 API discussed in Recipes 4285 and 4297.

We start with the meat of the solution: Subclassing MapView, defining our OnLongpressListener interface, and implementing the logic to catch when a user performs a longpress.

public class MyCustomMapView extends MapView {
 
    // Define the listener interface we will make use of in our MapActivity later.
    public interface OnLongpressListener {
        public void onLongpress(MapView view, GeoPoint longpressLocation);
    }
 
    // Time in ms before the OnLongpressListener is triggered.
    static final int LONGPRESS_THRESHOLD = 500;

    /*
     * The Timer will be instrumental in detecting our longpresses. It executes a
     * task after a given amount of time.
     */
    private Timer longpressTimer = new Timer();

    /*
     * Our OnLongPressListener instance. When a longpress is detected, its
     * onLongPress() method is called.
     */ 
    private MyCustomMapView.OnLongpressListener longpressListener;
 
    /*
     * Keep a record of the center of the map, to know if the map
     * has been panned.
     */
    private GeoPoint lastMapCenter;
 
    public MyCustomMapView(Context context, String apiKey) {
        super(context, apiKey);
    }
 
    public MyCustomMapView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
 
    public MyCustomMapView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
 
    public void setOnLongpressListener(MyCustomMapView.OnLongpressListener listener) {
        longpressListener = listener;
    }
 
    /*
     * This method is called by Android every time user touches the map,
     * drags a finger on the map, or removes finger from the map.
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // Perform our custom logic.
        handleLongpress(event);
        
        return super.onTouchEvent(event);
    }
 
    /*
     * This method takes MotionEvent as argument and decides whether
     * or not a longpress has been detected.
     *
     * The Timer class executes a TimerTask after a given time,
     * and we start the timer when a finger touches the screen.
     *
     * We then listen for map movements or the finger being
     * removed from the screen. If any of these events occur
     * before the TimerTask is executed, it gets cancelled. Else
     * the OnLongPressListener.onLongpress() method is fired.
     */
    private void handleLongpress(final MotionEvent event) {
 
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            // Finger has touched screen.
            longpressTimer = new Timer();
            longpressTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    GeoPoint longpressLocation = getProjection().fromPixels((int)event.getX(),
                            (int)event.getY());
 
                    /*
                     * Fire the listener. We pass the map location
                     * of the longpress as well, in case it is needed
                     * by the caller.
                     */
                    longpressListener.onLongpress(MyCustomMapView.this, longpressLocation);
                }
 
            }, LONGPRESS_THRESHOLD);
 
            lastMapCenter = getMapCenter();
        }
 
        if (event.getAction() == MotionEvent.ACTION_MOVE) {
 
            if (!getMapCenter().equals(lastMapCenter)) {
                // User is panning the map, this is no longpress
                longpressTimer.cancel();
            }
 
            lastMapCenter = getMapCenter();
        }
 
        if (event.getAction() == MotionEvent.ACTION_UP) {
            // User has removed finger from map.
            longpressTimer.cancel();
        }
 
            if (event.getPointerCount() > 1) {
                        // This is a multitouch event, probably zooming.
                longpressTimer.cancel();
            }
    }
}

You will need to modify your map layout file, so that we make use of the MyCustomMapView we just defined:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <com.example.MyCustomMapView android:id="@+id/mapview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:apiKey="<YOUR MAP API KEY HERE>"
        android:clickable="true"/>
</RelativeLayout>

Make note of the android:clickable attribute. As you might know, this must be set to be able to pan, zoom or in other ways interact with your map.

The last piece is adding your onLongpressListener instance to the MapView in your MapActivity. For the sake of the example, let'™s say the previous layout file is named res/layout/map.xml. The necessary code for adding an OnLongPressListener will look something like this.

public class Map extends MapActivity {
    private MyCustomMapView mapView;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        // Add our map layout to this MapActivity
        setContentView(R.layout.map);
 
        // Add the OnLongPressListener to our custom MapView
        mapView = (MyCustomMapView)findViewById(R.id.mapview);
        mapView.setOnLongpressListener(new MyCustomMapView.OnLongpressListener() {
        public void onLongpress(final MapView view, final GeoPoint longpressLocation) {
            runOnUiThread(new Runnable() {
            public void run() {
                /*
                 * Insert your longpress action here!
                 */
            }
        });
        }
    });

To actually have your longpress open up a context menu, you need to perform some additional setup of the context menu itself. I'™ve avoided including this, to make the example shorter and hopefully clearer. To test that it works, try for example adding a log statement.

No records found.