Monday, March 1, 2010

 

Responding to zooms and pans in Android's MapView

There's a curious lacuna in Android's MapView API: it's (relatively) easy to display a map, and overlay items atop a map; it's (relatively) easy to detect when an overlay item has been touched; but there's no straightforward way to work out when the user has touched anywhere else on the map. Because of the way MapView works, the usual onTouchListener() solution only works for one touch, and then fails.

There is, however, a solution. Not a pretty one, but it works. The solution is to use your own subclass of MapView:


public class ITRMapView extends MapView {
public ITRMapView(android.content.Context context, android.util.AttributeSet attrs) {
super(context, attrs);
}

public ITRMapView(android.content.Context context, android.util.AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}

public ITRMapView(android.content.Context context, java.lang.String apiKey) {
super(context, apiKey);
}
}


and within that subclass, override onTouchEvent. You're probably only interested in the UP action:

public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction()==MotionEvent.ACTION_UP) {
//do your thing
}
return super.onTouchEvent(ev);
}


But what if you want to detect zoom actions as well as touches? Simple, thought I: just override onDraw() and do the same thing. But you can't - MapView's onDraw() is final!

Catastrophe? Not quite. It turns out that overriding dispatchDraw() works just fine:

int oldZoomLevel=-1;

public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (getZoomLevel() != oldZoomLevel) {
//do your thing
oldZoomLevel = getZoomLevel();
}
}


Et voila - you can programmatically detect and respond when the user pans and/or zooms your map. Really not so hard after all.

Labels: , , , , , , , , , ,


Comments:
Jon,
Do I need something in the Manifest?
This breaks when I run with my MapView subclass.

mapView = (MyMapViewSubclass)findViewById(R.id.mapview);
 
Yes - you need a layout entry like
<my.packet.name.MyMapView
android:id="@+id/mapview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:apiKey="MyGoogleMapsAPIKey"
android:clickable="true" />
 
Sorry, yes I have that...
My map view worked fine until I tried to cast it to my subclass.
Not sure why it's not happy?

Is overriding onUserInteraction() equivalent to overriding dispatchDraw?
 
Thank you SO much for posting this. I had a sinking feeling that I'd have to extend the MapView object but tried to find a easier solution. Now if I can just get the ProgressDialog to appear while I'm pulling results from the network I'll be all set! Thanks again.
 
you are great. Thank you.
 
Hi, can you share source code?
 
Sounds great.
Problem is: It does not work.
pan-events by the user are not detected. :/
 
Nice approach. But have you tried the OnOverlayGestureListener? Wouldn't it be a bit easier?

http://code.google.com/p/mapview-overlay-manager/wiki/OnOverlayGestureListener
 
Wouldn't overriding onSizeChanged() be better to override than dispatchDraw()? One would assume that the name "onSizeChanged()" implies it is related to zooming after all.
 
Hi Jon,

I am following your code for listening to the zooming action. But it cannot even load, always with the "force close" message from the beginning.

I have the following code in my MapActivity class file "public class Ngb2Map extends MapActivity":
private ITRMapView mapView;
mapView = (ITRMapView)findViewById(R.id.mapview);

and "<nottingham.ngb2.ITRMapView ..." instead of "<com.google.android.maps.MapView" in the main.xml file.

I think the error comes from "(ITRMapView)findViewById(R.id.mapview);", but do not know what it should be.

How should I initialize the subclass ITRMapView?

Thanks a lot!
 

Post a Comment

Subscribe to Post Comments [Atom]





<< Home

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]