Abdullah Diab’s Blog

Android Map View With Pan & Zoom Events

I was working an Android application, and I had an activity that contains a map in it, the map is hosted inside a MapView. The application had to show some pins over the map, the pins locations are obtained from a web service. I had to retrieve the pins locations after the user pans the map or zooms in or out.

Google didn’t provide events for panning and zooming inside MapView class, but you can extend the class and do it your self, and that’s what I did.

Listener Interface

import com.google.android.maps.GeoPoint;

public interface MapViewListener {
	void onPan(GeoPoint oldTopLeft,
			   GeoPoint oldCenter,
			   GeoPoint oldBottomRight,
			   GeoPoint newTopLeft,
			   GeoPoint newCenter,
			   GeoPoint newBottomRight);
	void onZoom(GeoPoint oldTopLeft,
				GeoPoint oldCenter,
				GeoPoint oldBottomRight,
				GeoPoint newTopLeft,
				GeoPoint newCenter,
				GeoPoint newBottomRight,
				int oldZoomLevel,
				int newZoomLevel);
	void onClick(GeoPoint clickedPoint);
}

This listener will receive three events, onPan when the user pans the map, onZoom when the user zooms in or out, onClick when the user taps on the map without panning.

Each event will pass useful parameters, which are old and new values for the top left corner, bottom right corner, center point and zoom level.

Map View Code

The new map view will inherit the MapView class, and define some useful variables:

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;

public class ObservableMapView extends MapView {
	public ObservableMapView(Context context, String apiKey) { super(context, apiKey); }
	public ObservableMapView(Context context, AttributeSet attrs) { super(context, attrs); }
	public ObservableMapView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); }
	
	private GeoPoint mOldTopLeft;
	private GeoPoint mOldCenter;
	private GeoPoint mOldBottomRight;
	private int mOldZoomLevel = -1;
	
	private MapViewListener mMapViewListener;
	public MapViewListener getMapViewListener() { return mMapViewListener; }
	public void setMapViewListener(MapViewListener value) { mMapViewListener = value; }

// ...
}

To get touch events we’re going to override the onTouchEvent:

@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if (ev.getAction() == MotionEvent.ACTION_UP) {
			GeoPoint newCenter = this.getMapCenter();
			GeoPoint newTopLeft = this.getProjection().fromPixels(0, 0);
			GeoPoint newBottomRight = this.getProjection().fromPixels(this.getWidth(), this.getHeight());
			
			if (this.mMapViewListener != null &&
				newTopLeft.getLatitudeE6() == mOldTopLeft.getLatitudeE6() &&
				newTopLeft.getLongitudeE6() == mOldTopLeft.getLongitudeE6()) {
				mMapViewListener.onClick(this.getProjection().fromPixels((int)ev.getX(), (int)ev.getY()));
			}
		}
		return super.onTouchEvent(ev);
	}

Here we’re checking the new coordinates with the old coordinates, if they haven’t changed then the user has tapped on the map, but if they changed then it is a panning event, we’re not going to emit the event of panning now.

To be able to find if the user has zoomed we’re going to override the dispatchDraw method, because it will be called when the user pans or zooms, so it is the place we’re going to check for events in it.

@Override
	protected void dispatchDraw(Canvas canvas) {
		super.dispatchDraw(canvas);
		
		GeoPoint newCenter = this.getMapCenter();
		GeoPoint newTopLeft = this.getProjection().fromPixels(0, 0);
		GeoPoint newBottomRight = this.getProjection().fromPixels(this.getWidth(), this.getHeight());
		int newZoomLevel = this.getZoomLevel();
		
		if (mOldCenter == null)
			mOldCenter = newCenter;
		
		if (mOldTopLeft == null)
			mOldTopLeft = newTopLeft;

		if (mOldBottomRight == null)
			mOldBottomRight = newBottomRight;

		if (newTopLeft.getLatitudeE6() != mOldTopLeft.getLatitudeE6() || newTopLeft.getLongitudeE6() != mOldTopLeft.getLongitudeE6()) {
			if (this.mMapViewListener != null) {
				GeoPoint oldTopLeft, oldCenter, oldBottomRight;
				
				oldTopLeft = mOldTopLeft;
				oldCenter = mOldCenter;
				oldBottomRight = mOldBottomRight;
		
				mOldBottomRight = newBottomRight;
				mOldTopLeft = newTopLeft;
				mOldCenter = newCenter;
				
				mMapViewListener.onPan(oldTopLeft,
									   oldCenter,
									   oldBottomRight,
									   newTopLeft,
									   newCenter,
									   newBottomRight);
			}
		}
		
		if (mOldZoomLevel == -1)
			mOldZoomLevel = newZoomLevel;
		else if (mOldZoomLevel != newZoomLevel && mMapViewListener != null) {
			int oldZoomLevel = mOldZoomLevel;
			GeoPoint oldTopLeft, oldCenter, oldBottomRight;
			oldTopLeft = mOldTopLeft;
			oldCenter = mOldCenter;
			oldBottomRight = mOldBottomRight;
	
			mOldZoomLevel = newZoomLevel;
			mOldBottomRight = newBottomRight;
			mOldTopLeft = newTopLeft;
			mOldCenter = newCenter;
			
			mMapViewListener.onZoom(oldTopLeft,
									oldCenter,
									oldBottomRight,
									newTopLeft,
									newCenter,
									newBottomRight,
									oldZoomLevel,
									newZoomLevel);
		}
	}

Download

The source code is licensed under the GNU Public License (GPL).

You can download the source code from here:

Project on GitHub

Source code from GitHub