I’ve prevously written about my attempts to capture longpress/longclick on a map (see this post). As I wrote in that post:
“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(). ”
The solution I outlined in the previous post never did feel quite right. It was a bit of a mess and it could be difficult to follow what was really happening. I recently improved this design in an app of mine, and here’s the lowdown:
The main steps are as follows:
- Subclass
MapViewand define theOnLongpressListener. - In your layout file, set up the new
MapViewsubclass. - Define an instance of your new
OnLongpressListenerin yourMapActivity.
The first point is the most important, this is the core of the solution: Subclassing MapView and adding our custom OnLongpressListener. I hope the code comments make it clear enough what is going on:
public class MyCustomMapView extends MapView {
// Define the interface we will interact with from our Map
public interface OnLongpressListener {
public void onLongpress(MapView view, GeoPoint longpressLocation);
}
/**
* Time in ms before the OnLongpressListener is triggered.
*/
static final int LONGPRESS_THRESHOLD = 500;
/**
* Keep a record of the center of the map, to know if the map
* has been panned.
*/
private GeoPoint lastMapCenter;
private Timer longpressTimer = new Timer();
private MyCustomMapView.OnLongpressListener longpressListener;
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 every time user touches the map,
* drags a finger on the map, or removes finger from the map.
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
handleLongpress(event);
return super.onTouchEvent(event);
}
/**
* This method takes MotionEvents and decides whether or not
* a longpress has been detected. This is the meat of the
* OnLongpressListener.
*
* 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 listener is fired.
*
* @param event
*/
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();
}
}
}
The layout file will look somewhat like the following:
<?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 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 final piece is adding your onLongpressListener 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 your MapActivity will look something like this (I only include the parts relevant for this example:
public class Map extends MapActivity {
private MyCustomMapView mapView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.map);
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 clearer. Any tutorial on setting up context menus should be able to guide you in the right direction.
If something is unclear in the examples, or you have trouble getting stuff working, just add a comment and I’ll try to help you out.
14 comments
3 pings
Hanna says:
May 4, 2011 at 21:25 (UTC 1 )
Thanks for your posts about the longClicks on a map!
I’ve spent a lot of time finding a solution for this problem and I like your solution…
Now I only have to make this work for the context menu as well ;-)
Regards, Hanna
Roger Kind Kristiansen says:
May 5, 2011 at 05:38 (UTC 1 )
Hi Hanna, glad you found it helpful! Just let me know If you can’t get it working with the context menu. Maybe I should add a small post about that? :)
Frank says:
May 10, 2011 at 10:53 (UTC 1 )
I’m trying to get this working myself but I can’t use GeoPoint longpressLocation in the // Insert your longpress action here part of the code as Geopoint is not final. Any ideas?
Roger Kind Kristiansen says:
May 10, 2011 at 11:16 (UTC 1 )
Frank, I updated the example. Just try making the GeoPoint final in the onLongpress call.
Darren says:
July 7, 2011 at 22:15 (UTC 1 )
Hey, I tried using your solution and the long presses seem to work fine, but I am not able to zoom on the map even though the android:clickable attribute is set. Any ideas?
Darren says:
July 7, 2011 at 23:16 (UTC 1 )
Also if the map is panned while the longpress occurs, it still fires the event and places a marker on the screen(in the wrong place).
Roger Kind Kristiansen says:
July 8, 2011 at 08:28 (UTC 1 )
Hi Darren,
I don’t have an idea right away, but regarding the panning issue there is code to explicitly avoid triggering the event while panning. Could you maybe send me your code, and I can have a look at it?
Fernando says:
October 17, 2011 at 19:03 (UTC 1 )
Thank you very much!!! It works! It’s been a complete nightmare until getting here. It’s the only solution I liked at all.
Iván says:
November 21, 2011 at 11:58 (UTC 1 )
Thank you very much for your code, it’s the best! But i’, having any problems with a google map key, when i try my layout with a Google’s apiKey give me an error: “error inflating class”. My code:
How I can make work this? Error from the name of package? I hope you can help me, more hehe Great day.
Iván says:
November 21, 2011 at 12:01 (UTC 1 )
Hi again! And sorry, i had my package name bad, just work perfectly!!! Thank you very very much! :D
max says:
November 28, 2011 at 12:04 (UTC 1 )
Hey Roger,
BIG BIG BIG thank you from me. Thank god I found your tutorial about this issue.
Greeting from Germany
BRH says:
December 8, 2011 at 12:41 (UTC 1 )
Do you think it would be useful to check the pointer count (event.getPointerCount() > 1) in the ACTION_DOWN case so you won’t start the counter in multitouch
Mark Thorogood says:
December 26, 2011 at 18:16 (UTC 1 )
Phenomenal example. On point, and well written. Many thanks for sharing.
exequiel says:
January 30, 2012 at 19:44 (UTC 1 )
Groxxx! work for me!
Handling longpress/longclick in MapActivity » Roger Kind Kristiansen says:
April 19, 2011 at 08:45 (UTC 1 )
[...] Android: Handling longpress/longclick on map (revisited) » Roger Kind Kristiansen says: [...]
Trouble creating custom mapview for longclick? | appsgoogleplus.com says:
February 7, 2012 at 04:43 (UTC 1 )
[...] I am trying to get the geopoint from a longpress on a map. So far, when I run the activity which implements the custom class I get an error when the activity tries to open. I am trying to get the method for registering a long click from here: http://www.kind-kristiansen.no/2011/android-handling-longpresslongclick-on-map-revisited/ [...]
Trouble creating custom mapview for longclick? | Software development support, software risk,bugs for bugs, risk analysis, says:
February 8, 2012 at 01:01 (UTC 1 )
[...] Trouble creating custom mapview for longclick? I am trying to get the geopoint from a longpress on a map. So far, when I run the activity which implements the custom class I get an error when the activity tries to open. I am trying to get the method for registering a long click from here: http://www.kind-kristiansen.no/2011/android-handling-longpresslongclick-on-map-revisited/ [...]